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);