Reputation: 1
I'm trying to implement a confirmation modal that appears when the user tries to navigate away from a page with unsaved changes. The modal correctly appears when the browser's back button or a "previous" button on the page is clicked, but when I confirm the action, the modal enters an infinite loop, continuously opening again instead of navigating away. This issue only occurs with the back button or the "previous" button; when navigating via other links, everything works as expected.
The modal works fine when clicking on links or buttons that trigger navigation to a specific path (e.g., from a header link), but it enters an infinite loop when confirming navigation triggered by the browser's back button or the "Previous" button in the form.
Without window.history.pushState(null, '', location.pathname + location.search);, the modal doesn't appear at all on back navigation, but with it, the modal keeps reappearing in an infinite loop when trying to confirm back navigation.
How can I prevent the modal from entering an infinite loop and correctly navigate away when the user confirms the back navigation?
`const EstimateRequestPage = () => {
const navigate = useNavigate();
const location = useLocation();
const params = new URLSearchParams(location.search);
const [isModalOpen, setIsModalOpen] = useState(false);
const { isDirty, setIsDirty } = useIsDirty();
const { handleNavigate, renderModal } = usePreventLeave(isDirty);
return (
<Container>
<form onSubmit={handleSubmit}>
{/* Form fields... */}
<ButtonGroup>
<PreviousButton variant="outlined" onClick={() => handleNavigate(-1)}>
Previous
</PreviousButton>
<RequestButton variant="contained" type="submit">
Submit
</RequestButton>
</ButtonGroup>
</form>
{renderModal()}
</Container>
);
};`
`import { useState, useCallback, useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import ConfirmationModal from '@/components/ui/modal/Modal';
export const usePreventLeave = (isDirty) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [pendingPath, setPendingPath] = useState(null);
const navigate = useNavigate();
const location = useLocation();
useEffect(() => {
const handlePopState = (event) => {
if (isDirty) {
window.history.pushState(null, '', location.pathname + location.search);
setIsModalOpen(true);
}
};
window.history.pushState(null, '', location.pathname + location.search);
window.addEventListener('popstate', handlePopState);
return () => {
window.removeEventListener('popstate', handlePopState);
};
}, [isDirty, location.pathname, location.search]);
const handleNavigate = useCallback(
(path) => {
if (isDirty) {
setPendingPath(path);
setIsModalOpen(true); // Show modal
} else {
navigate(path); // Navigate if not dirty
}
},
[isDirty, navigate]
);
const handleConfirm = useCallback(() => {
setIsModalOpen(false);
if (pendingPath === null) {
navigate(-1); // Back navigation
} else if (pendingPath) {
navigate(pendingPath); // Navigate to pending path
}
setPendingPath(null);
}, [navigate, pendingPath]);
const handleCancel = useCallback(() => {
setIsModalOpen(false);
setPendingPath(null); // Reset state
}, []);
return {
handleNavigate,
renderModal: () => (
<ConfirmationModal
open={isModalOpen}
handleClose={handleCancel}
handleConfirm={handleConfirm}
messageId1="Leave page?"
messageId2="Unsaved changes will be lost."
confirmButtonId="confirm_button"
/>
),
setPendingPath,
};
};
`
Upvotes: 0
Views: 63
Reputation: 1
The problem is when you confirm navigation, the handleNavigate function might trigger a state change that causes handlePopState to run again, leading to an infinite loop. Try the code below i have provided use event.preventDefault() in place of window.history.pushState() and also add event listener and remove event listener once component unmounts & also remove location.pathname and location.search from useeffect dependency array.
useEffect(() => {
const handlePopState = (event) => {
if (isDirty) {
// Prevent the default behavior and show the modal
event.preventDefault();
setIsModalOpen(true);
}
};
// Adding event listener for popstate
window.addEventListener('popstate', handlePopState);
// Clean up the event listener on component unmount
return () => {
window.removeEventListener('popstate', handlePopState);
};
}, [isDirty]);
Upvotes: 0