I was tried to do exact same thing with modal open/close and make user open modal by forward button and close it by back button.
I see all answers but I think its better to do it with hook
This is a hook I end up with it.
When the modal state set to open, I replaces the current history state because popstate event give you state of the current page and it called after page loaded (see here), I also push a new state when modal opened,
So now we have 2 states in history, first one is closeModal and second one is openModal, now when the user changed the history we can know what we need to do (opening or closing the modal).
export function useModalHistory(
id: string,
isOpen: boolean,
onChange: (open: boolean) => void,
) {
useEffect(() => {
if (id && isOpen) {
// set new states to history when isOpen is true
// but we need to check isOpen happened from `popstate` event or not
// so we can prevent loop
if (window.history.state?.openModal !== id) {
window.history.replaceState({closeModal: id}, '');
window.history.pushState({openModal: id}, '', window.location.href);
}
return () => {
// only close modal if the closing is not from `popstate` event
if (window.history.state?.closeModal !== id) window.history.back();
};
}
}, [id, isOpen]);
useEventListener('popstate', event => {
if (event.state?.closeModal === id) {
onChange(false);
}
if (event.state?.openModal === id) {
onChange(true);
}
});
}
Also note I used useEventListener from https://usehooks-ts.com/react-hook/use-event-listener, you can either create your hook or use it from package.
If you use react-router you can write it like this
export function useModalHistory(
id: string | undefined,
isOpen: boolean,
onChange: (open: boolean) => void,
) {
const history = useHistory<{openModal?: string; closeModal?: string}>();
useEffect(() => {
if (id && isOpen) {
if (history.location.state?.openModal !== id) {
history.replace({state: {closeModal: id}});
history.push({state: {openModal: id}});
}
return () => {
if (history.location.state?.closeModal !== id) history.goBack();
};
}
}, [id, isOpen, history]);
useEventListener('popstate', event => {
if (id) {
if (event.state.state?.closeModal === id) {
onChange(false);
}
if (event.state.state?.openModal === id) {
onChange(true);
}
}
});
}
Usage
const [isModalOpen, setIsModalOpen] = useState(false);
// be aware id need to be unique for each modal
useModalHistory('my_modal', isModalOpen, setIsModalOpen);