Home > Software engineering >  React Router useLocation() location is not followed to the current page
React Router useLocation() location is not followed to the current page

Time:03-30

I'm using react-router-dom: "^6.2.2" in my project for a long time, but I don't know before that this version is not included useBlocker() and usePrompt(). So I'm found this The dialog image if it works correctly (Not pop-up when pressed anything except changing route.)

There is a bug that the dialog is poped-up when clicked at the menu bar button. When I clicked discard change the page is not changed. There is a bug that the dialog is poped-up when clicked at the menu bar button. When I clicked discard change the page is not changed.

Thank you, any help is appreciated.

CodePudding user response:

From what I can see your useNavigationBlockerController hook handleNavigationBlocking memoized callback is missing a dependency on the location.pathname value. In other words, it is closing over and referencing a stale value.

Add the missing dependencies:

const navigationBlockerContext = createContext();

...

function useNavigationBlockerHandler(
  navigationBlockerHandler,
  canShowDialogPrompt
) {
  const navigator = useContext(UNSAFE_NavigationContext).navigator;

  useEffect(() => {
    if (!canShowDialogPrompt) return;

    // For me, this is the dark part of the code
    // maybe because I didn't work with React Router 5,
    // and it emulates that
    const unblock = navigator.block((tx) => {
      const autoUnblockingTx = {
        ...tx,
        retry() {
          unblock();
          tx.retry();
        }
      };
      navigationBlockerHandler(autoUnblockingTx);
    });
    return unblock;
  });
}

...

function useNavigationBlockerController(canShowDialogPrompt) {
  // It's look like this function is being re-rendered before routes done that cause the useLocation() get the previous route page.
  const navigate = useNavigate();
  const currentLocation = useLocation();
  const [showDialogPrompt, setShowDialogPrompt] = useState(false);
  const [wantToNavigateTo, setWantToNavigateTo] = useState(null);
  const [isNavigationConfirmed, setIsNavigationConfirmed] = useState(false);

  const handleNavigationBlocking = useCallback(
    (locationToNavigateTo) => {
      // currentLocation.pathname is the previous route but locationToNavigateTo.location.pathname is the current route
      if (
        !isNavigationConfirmed &&
        locationToNavigateTo.location.pathname !== currentLocation.pathname
      ) {
        setShowDialogPrompt(true);
        setWantToNavigateTo(locationToNavigateTo);
        return false;
      }
      return true;
    },
    [isNavigationConfirmed, currentLocation.pathname] // <-- add current pathname
  );

  const cancelNavigation = useCallback(() => {
    setIsNavigationConfirmed(false);
    setShowDialogPrompt(false);
  }, []);

  const confirmNavigation = useCallback(() => {
    setIsNavigationConfirmed(true);
    setShowDialogPrompt(false);
  }, []);

  useEffect(() => {
    if (isNavigationConfirmed && wantToNavigateTo) {
      navigate(wantToNavigateTo.location.pathname);
      setIsNavigationConfirmed(false);
      setWantToNavigateTo(null);
    }
  }, [isNavigationConfirmed, navigate, wantToNavigateTo]); // <-- add navigate

  useNavigationBlockerHandler(handleNavigationBlocking, canShowDialogPrompt);

  return [showDialogPrompt, confirmNavigation, cancelNavigation];
}

...

export function NavigationBlockerProvider({ children }) {
  const [showDialogLeavingPage, setShowDialogLeavingPage] = useState(false);
  const [
    showDialogPrompt,
    confirmNavigation,
    cancelNavigation
  ] = useNavigationBlockerController(showDialogLeavingPage);

  return (
    <navigationBlockerContext.Provider
      value={{ showDialog: setShowDialogLeavingPage }}
    >
      <LeavingPageDialog
        showDialog={showDialogPrompt}
        setShowDialog={setShowDialogLeavingPage}
        cancelNavigation={cancelNavigation}
        confirmNavigation={confirmNavigation}
      />
      {children}
    </navigationBlockerContext.Provider>
  );
}

...

export const useNavigationBlocker = () => {
  return useContext(navigationBlockerContext);
};

Edit react-router-uselocation-location-is-not-followed-to-the-current-page

  • Related