3

I would like to detect when the user leaves the page Next JS. I count 3 ways of leaving a page:

  1. by clicking on a link
  2. by doing an action that triggers router.back, router.push, etc...
  3. by closing the tab (i.e. when beforeunload event is fired

Being able to detect when a page is leaved is very helpful for example, alerting the user some changes have not been saved yet.

I would like something like:

router.beforeLeavingPage(() => {
    // my callback
})
Chukwu3meka
  • 3,547
  • 4
  • 26
  • 44
  • You can use the `beforePopState` event to detect changes to the session history navigation as suggested in [Want to have an event handler for the browser's back button with next.js](https://stackoverflow.com/questions/61932918/want-to-have-an-event-handler-for-the-browsers-back-button-with-next-js). This covers both your point **1.** and **2.**. – juliomalves Jan 25 '22 at 00:07

7 Answers7

4

You can use default web api's eventhandler in your react page or component.

if (process.browser) {
  window.onbeforeunload = () => {
    // your callback
  }
}
Darryl RN
  • 5,634
  • 2
  • 23
  • 37
3

You can use router.beforePopState check here for examples

Cyber Progs
  • 3,240
  • 3
  • 27
  • 36
  • 1
    Here's a practical example on how to use the `beforePopState` event to detect back/forward actions: [Want to have an event handler for the browser's back button with next.js](https://stackoverflow.com/a/69569682/1870780). – juliomalves Dec 03 '21 at 21:29
2

this worked for me in next-router / react-FC

  1. add router event handler
  2. add onBeforeUnload event handler
  3. unload them when component unmounted

https://github.com/vercel/next.js/issues/2476#issuecomment-563190607

blue-hope
  • 1,672
  • 1
  • 7
  • 10
2

router.beforePopState is great for browser back button but not for <Link>s on the page.

Solution found here: https://github.com/vercel/next.js/issues/2694#issuecomment-732990201

... Here is a version with this approach, for anyone who gets to this page looking for another solution. Note, I have adapted it a bit further for my requirements.

// prompt the user if they try and leave with unsaved changes  
useEffect(() => {
  const warningText =
    'You have unsaved changes - are you sure you wish to leave this page?';
  const handleWindowClose = (e: BeforeUnloadEvent) => {
    if (!unsavedChanges) return;
    e.preventDefault();
    return (e.returnValue = warningText);
  };
  const handleBrowseAway = () => {
    if (!unsavedChanges) return;
    if (window.confirm(warningText)) return;
    router.events.emit('routeChangeError');
    throw 'routeChange aborted.';
  };
  window.addEventListener('beforeunload', handleWindowClose);
  router.events.on('routeChangeStart', handleBrowseAway);
  return () => {
    window.removeEventListener('beforeunload', handleWindowClose);
    router.events.off('routeChangeStart', handleBrowseAway);
  };
}, [unsavedChanges]);

So far, it seems to work pretty reliably.

Alternatively you can add an onClick to all the <Link>s yourself.

700 Software
  • 81,209
  • 77
  • 221
  • 333
1

I use 'next/router' like NextJs Page for disconnect a socket

import { useEffect } from 'react'
import { useRouter } from 'next/router'

export default function MyPage() {
    const router = useRouter()

    useEffect(() => {
        const exitingFunction = () => {
            console.log('exiting...');
        };

        router.events.on('routeChangeStart', exitingFunction );

        return () => {
            console.log('unmounting component...');
            router.events.off('routeChangeStart', exitingFunction);
        };
    }, []);

    return <>My Page</>
}
Luis Moreno
  • 435
  • 3
  • 5
1

I saw two things when coding it :

  • Knowing when nextjs router would be activated
  • Knowing when specific browser event would happen

I did a hook that way. It triggers if next router is used, or if there is a classic browser event (closing tab, refreshing)

import SingletonRouter, { Router } from 'next/router';

export function usePreventUserFromErasingContent(shouldPreventLeaving) {
  const stringToDisplay = 'Do you want to save before leaving the page ?';

  useEffect(() => {
    // Prevents tab quit / tab refresh
    if (shouldPreventLeaving) {
      // Adding window alert if the shop quits without saving
      window.onbeforeunload = function () {
        return stringToDisplay;
      };
    } else {
      window.onbeforeunload = () => {};
    }

    if (shouldPreventLeaving) {
      // Prevents next routing
      SingletonRouter.router.change = (...args) => {
        if (confirm(stringToDisplay)) {
          return Router.prototype.change.apply(SingletonRouter.router, args);
        } else {
          return new Promise((resolve, reject) => resolve(false));
        }
      };
    }
    return () => {
      delete SingletonRouter.router.change;
    };
  }, [shouldPreventLeaving]);
}

You just have to call your hook in the component you want to cover :

usePreventUserFromErasingContent(isThereModificationNotSaved);

This a boolean I created with useState and edit when needed. This way, it only triggers when needed.

Yoann Buzenet
  • 171
  • 11
-2

You can use the react-use npm package

import { useEffect } from "react";
import Router from "next/router";
import { useBeforeUnload } from "react-use";

export const useLeavePageConfirm = (
  isConfirm = true,
  message = "Are you sure want to leave this page?"
) => {
  useBeforeUnload(isConfirm, message);

  useEffect(() => {
    const handler = () => {
      if (isConfirm && !window.confirm(message)) {
        throw "Route Canceled";
      }
    };

    Router.events.on("routeChangeStart", handler);

    return () => {
      Router.events.off("routeChangeStart", handler);
    };
  }, [isConfirm, message]);
};
Embedded_Mugs
  • 1,669
  • 3
  • 16
  • 24