DEV Community

Cover image for Detecting user leaving page with react-router-dom v6.0.2
Muhammad Bilal Bangash
Muhammad Bilal Bangash

Posted on

Detecting user leaving page with react-router-dom v6.0.2

Introduction:

Before reading this article you should know about React Routing, its working.

Main Focus of this post is detecting user leaving page with react-router-dom v6.0.2.

You can use usePrompt or useBlocker to detect and show a prompt before leaving to another route if they have any unsaved changes.

However, in the official documentation of react router v6 the following is mentioned:

from v5 (along with usePrompt and useBlocker from the v6 betas) are not included in the current released version of v6.

But there are two different solution to achieve your goal.

  1. Either you can downgrade to v5 or 6.0.0-alpha.5 to use usePrompt & useBlocker in you project/code

  2. Second solution is create custom hook instead of downgrading.

Post Focus useCallbackPrompt & useBlocker custom hooks

In my today's post I will focus on my second solution how did I created a custom hook to resolve my problem.

for this I created a small project for demo.
Here is the project link GITHUB.

In this project I created two routes for Home and About page and in Home Page there is simple form with Name and Designation Field.
My goal was when user type something on form and trying to leave this page or route prompt/DialogBox will be shown that there are some changes.....

how my folder structure looks like
useCallbackPrompt Hook
in above screenshot I highlighted the custom hooks that I created in this project

  • useBlocker

  • useCallbackPrompt

useCallbackPrompt Hook

useCallbackPrompt Hook
this hooks returns three things 2 boolean variables and 1 function. Basically for handling DialogBox to show or hide
here is the exact file for that useCallbackPrompt Hook

useBlocker Hook

useBlocker Hook

this hook basically blocks user from navigating away if there are any changes

useBlocker Hook

Now Question arise How I am using this in my project

useBlocker Hook Code
What I am doing here when user type something handleChange will trigger and update the showDialog to true and I am passing showDialog to useCallbackPrompt and when user trying to navigate away a prompt will be shown
Home page Code Screenshot

Home Page

Here is Live Demo Link

Here is Git Repo Link

Further improvement, suggestion or help. Welcome :)

Top comments (15)

Collapse
 
mleister97 profile image
mleister97

Small bug:

Make sure to also set the lastLocation to null inside the method cancelNavigation.

Why?

Imagine:

  1. Filling out a form
  2. Switch page
  3. "Are you sure dialog" will open
  4. Cancel the dialog (stay on page)
  5. Update the form values again
  6. Submit the form via the default submit button
  7. Hook will keep the last location reminded and switch page now, even if not intended
Collapse
 
miguelpalacio profile image
Miguel Palacio

Thanks a lot Muhammad! šŸ™šŸ¼

This was extremely helpful. Worked like a charm.

Collapse
 
miguelpalacio profile image
Miguel Palacio

I had to modify useCallbackPrompt a little bit, as it didn't work properly when the component wasn't unmounted after a "confirmed navigation".

export function useCallbackPrompt(when) {
    const navigate = useNavigate();
    const location = useLocation();
    const [showPrompt, setShowPrompt] = useState(false);
    const [lastLocation, setLastLocation] = useState(null);
    const [confirmedNavigation, setConfirmedNavigation] = useState(false);

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

    // handle blocking when user click on another route prompt will be shown
    const handleBlockedNavigation = useCallback(
        (nextLocation) => {
            // in if condition we are checking next location and current location are equals or not
            if (!confirmedNavigation && nextLocation.location.pathname !== location.pathname) {
                setShowPrompt(true);
                setLastLocation(nextLocation);
                return false;
            }
            return true;
        },
        // eslint-disable-next-line react-hooks/exhaustive-deps
        [confirmedNavigation, location]
    );

    const confirmNavigation = useCallback(() => {
        setShowPrompt(false);
        setConfirmedNavigation(true);
    }, []);

    useEffect(() => {
        if (confirmedNavigation && lastLocation) {
            navigate(lastLocation.location.pathname);

            // Clean-up state on confirmed navigation
            setConfirmedNavigation(false);
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [confirmedNavigation, lastLocation]);

    useBlocker(handleBlockedNavigation, when);

    return [showPrompt, confirmNavigation, cancelNavigation];
}

Enter fullscreen mode Exit fullscreen mode

The changes are:

  1. [confirmedNavigation, location] - added location to the handleBlockedNavigation hook. The location object changes and the location.pathname value remains outdated.
  2. setConfirmedNavigation(false); - set after the programmatic navigation, state needed to be cleaned up so that future prompts are displayed.

Sorry if I'm not clear enough. I'm short of time and cannot elaborate a lot. Hopefully it will be useful for someone.

Thanks.

Collapse
 
bangash1996 profile image
Muhammad Bilal Bangash

Thank you Miguel Palacio for fixing :)

Collapse
 
yinkash profile image
Adebiyi Adeyinka

Using React Router Dom V6, there is no navigator.block() function. use custom history and history.block() function instead, as mentioned in Drew Reese answer on stackoverflow

Collapse
 
proddy profile image
Proddy

Thanks Muhammad for this post. I've been after a nifty solution for this.

One enhancement would be to check if the form value has actually changed, before triggering the dialog? This would mean comparing values in handleChange() and keeping the original form values/states. Any ideas how to implement this?

Collapse
 
mouerr profile image
Mouerr • Edited

Hi thanks a lot.
When i click in page reload button, it shows the default browser alertbox. any idea how to solve it?

Collapse
 
jackwhisler1 profile image
Jack Whisler

Having the same issue. Have you made any progress?

Collapse
 
maria_felecan_25d3b5692a0 profile image
Maria Felecan

Could this be work with some updates if we have a multi-page form? e.g. different pathnames for each step "/form/step1" and "/form/step2"?

Collapse
 
ismailbutt6926 profile image
IsmailButt6926

can u please give me a solution with react router dom version 6.8.1 ...this code is not working in my case becuase it gives an error that navigator.block is not a function ...can any one help me?

Collapse
 
mandarfokmare007 profile image
Mandar Fokmare

Facing same issue,Did you get any solution.?

Collapse
 
sumiyasayeed profile image
Sumiya Sayeed

I am getting the following error

Image description
Any idea how to solve it?

Collapse
 
pankajthakur950 profile image
Pankaj Thakur

can we have custom popup for browser refresh as well. I tried your code & got the browser default only. Is there a way to show custom message on browser reload?

Collapse
 
jaisaro14 profile image
Jai Saravanan

Could we have custom message in the browser prompt?

Collapse
 
bangash1996 profile image
Muhammad Bilal Bangash

I think it's normal to not be able to control everything, it looks like we are not able to add custom functionality to onbeforeunload. developer.chrome.com/blog/chrome-5...
@jaisaro14 @jackwhisler1 @mouerr