27Lia
27Lia

Reputation: 1

Confirmation Modal in Infinite Loop on Browser Back Button

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

Answers (1)

Moiz ul haq
Moiz ul haq

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

Related Questions