I have a React component like this
export const NewComponent: React.FC<NewComponentProps> = ({prop1, prop2, prop3 }) => {
const getAbsPositionDialog = () => {
const element = document.getElementById('modalDialogId');
if (!element) {
return { x: 0, y: 0 };
}
const dialogPosition = element.getBoundingClientRect();
return { x: dialogPosition.x, y: dialogPosition.y };
}
const dialogPosition = getAbsPositionDialog();
const horizontal = dialogPosition.x + 100;
const vertical = dialogPosition.y + 100;
const toasterId = useId("toaster");
const { dispatchToast } = useToastController(toasterId);
const notify = (title: string) => {
dispatchToast(
<Toast>
<ToastTitle>{title}</ToastTitle>
</Toast>,
{ intent: "success" }
);
}
React.useEffect(() => {
<Toaster
toasterId={toasterId}
position="top-start"
offset={{horizontal: horizontal, vertical: vertical}}
pauseOnHover
pauseOnWindowBlur
timeout={1000}
/>
}, [horizontal, vertical]);
return (
<div>
<Button onClick={notify("Copied!")}/>
</div>
);
};
The idea is to show a Toast component when clicking on the button. This NewComponent
is shown within a modal dialog and the location of the Toast notification is set at the top-start
relative to the whole window screen size, so it works well like that, showing the Toast notification at that position. No problem with it.
The problem comes when I try to add some offset based on the modalDialogId
. I want to know where the dialog is located so I can move the Toast notification closer to it, but when the component is executed, the modal dialog is not loaded yet and therefore the offset always returns as 0, 0
.
I tried to use the React.useEffect
to render the Toaster
which is where I set my offset later, but that didn't seem to work.
Is there a way to achieve this?
for react-toaster you can set toaster postion style to absolute :
containerStyle= {{ position : "absolute"}}
and set the modal container position to relative
position: "relative",
demo :
import toast, { Toaster } from "react-hot-toast";
import Modal from "react-modal";
const dismissToast = () => toast.remove();
const successToast = () => toast.success("Here is your toast.");
export default function App() {
const customStyles = {
content: {
top: "20%",
left: "20%",
right: "20%",
bottom: "20%",
background: "#e5e5e5"
}
};
return (
<div>
<Modal
style={customStyles}
isOpen={true}
contentLabel="Minimal Modal Example"
>
<div style={{ position: "relative", margin: "auto", height: "100%" }}>
<button onClick={successToast}>Success toast</button>
<button onClick={dismissToast}>Remove all toasts</button>
<hr />
<h1>i am modal</h1>
<Toaster
position="bottom-left"
containerClassName="fff"
containerStyle={{ position: "absolute", inset: 0 }}
toastOptions={{
duration: 3000000,
iconTheme: {
primary: "red",
secondary: "white"
},
role: "status",
ariaLive: "polite",
style: {
background: "green",
color: "whitesmoke"
}
}}
/>
</div>
</Modal>
</div>
);
}
and the toast will be shown inside the modal without any extra code
for fluterui toaster you need to use callback ref to the modal and you need to add observer when the modal resized (note i use react-modal for the demo and you can create custom hook if you need):
import * as React from "react";
import { useState } from "react";
import {
useId,
Button,
Toaster,
useToastController,
ToastTitle,
Toast
} from "@fluentui/react-components";
import Modal from "react-modal";
export const DefaultToastOptions = () => {
const [x, setX] = useState(0);
const [y, setY] = useState(0);
const [isOpen, setIsOpen] = useState(false);
const toasterId = useId("toaster");
const { dispatchToast } = useToastController(toasterId);
const ref = React.useRef(null);
const dialogdRef = React.useCallback((node) => {
if (node !== null) {
ref.current = node;
const dialogPosition = ref.current.getBoundingClientRect();
setX(dialogPosition.x + 10);
setY(dialogPosition.y - 10);
}
}, []);
React.useEffect(() => {
const element = ref?.current;
if (!element) return;
const observer = new ResizeObserver((entries) => {
// 👉 Do something when the element is resized
const dialogPosition = entries[0].target.getBoundingClientRect();
setX(dialogPosition.x + 10);
setY(dialogPosition.y - 10);
console.log(entries[0]);
});
observer.observe(element);
return () => {
// Cleanup the observer by unobserving all elements
observer.disconnect();
};
}, [x, y]);
const notify = () => {
dispatchToast(
<Toast>
<ToastTitle>Options configured in Toaster </ToastTitle>
</Toast>,
{ intent: "info" }
);
};
const customStyles = {
content: {
top: "20%",
left: "20%",
right: "20%",
bottom: "20%",
background: "#e5e5e5"
}
};
React.useEffect(() => {
setIsOpen(true);
}, []);
return (
<>
<Modal
contentRef={dialogdRef}
style={customStyles}
isOpen={isOpen}
contentLabel="Minimal Modal Example"
>
<div>
<h1> I am a Modal</h1>
<Button onClick={notify}>Make toast</Button>
</div>
</Modal>
<Toaster
toasterId={toasterId}
offset={{ horizontal: x, vertical: y }}
position="top-end"
pauseOnHover
pauseOnWindowBlur
timeout={10000}
/>
</>
);
};