Detect when user leaves the page #9662
Replies: 28 comments 26 replies
-
+1 👍 for this feature. |
Beta Was this translation helpful? Give feedback.
-
Have you seen the docs for Router events ?. It may do the trick. |
Beta Was this translation helpful? Give feedback.
-
Yes, I seen it. But it doesn't. There is no way to cancel a route. |
Beta Was this translation helpful? Give feedback.
-
Any updates about this feature request? |
Beta Was this translation helpful? Give feedback.
-
I want to prevent go to back when they click browser back button or mouse back button too. |
Beta Was this translation helpful? Give feedback.
-
Needed this feature just now. |
Beta Was this translation helpful? Give feedback.
-
I think this functionality is essential for any website with forms or editable content to prevent data loss. React Router v6 has very convenient hooks for showing a See How can such functionality be realized within Next.js? I would have expected a very similar API in Next.js. I would contribute, but I'm not familiar with Next.js routing internals (React Router leverages React Context API). |
Beta Was this translation helpful? Give feedback.
-
I would certainly like this feature as well |
Beta Was this translation helpful? Give feedback.
-
A much needed feature 💯 |
Beta Was this translation helpful? Give feedback.
-
This is becoming a growing issue, please vercel hear our cry. useBeforeunload Hook (recommended) I expect it to do the work since it's built on react and I didn't seen any reliance on react-router |
Beta Was this translation helpful? Give feedback.
-
in a functional component
|
Beta Was this translation helpful? Give feedback.
-
Was looking for a solution similar to this and found something in the Next JS docs which has solved my problem. I was looking to use local storage when the user navigated to a different page/route. https://nextjs.org/docs/api-reference/next/router#usage-6 Might help if you're stuck like I was 🤘🏻 |
Beta Was this translation helpful? Give feedback.
-
Any news for this feature? |
Beta Was this translation helpful? Give feedback.
-
I have a custom hook. Works for me. 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]);
}; UPDATED: Should be considered use |
Beta Was this translation helpful? Give feedback.
-
Have y'all tried https://nextjs.org/docs/api-reference/next/router#routerbeforepopstate |
Beta Was this translation helpful? Give feedback.
-
Anyone have some rock solid approaches besides routeChangeStartand and beforeunload events? They seem to not work when the user navigates back and forth between the same pages. |
Beta Was this translation helpful? Give feedback.
-
Hello, I've created two hooks, one to detect browser refresh, the other to detect when the user is navigating to another pages (this includes back and forth navigation) For browser refresh, I've created my own hook since I don't like to add too many packages, but this is inspired by react-beforeunload. Here is my hook: import { useEffect } from 'react'
// My hook simple reset session storage when user trigger a browser refresh
export default function useBeforeUnload() {
useEffect(() => {
const eventListener = (event) => {
// you can do what you want here
sessionStorage.clear()
}
window.addEventListener('beforeunload', eventListener)
return () => {
window.removeEventListener('beforeunload', eventListener)
}
}, [])
} The second hook is to detect when the user is navigating to another pages. I need to save scroll position in order to restore when the user come back on a page. This is pretty troublesome in Next.js. import { useRef, useEffect } from 'react'
import { useRouter } from 'next/router'
// Scroll position management
export default function useScrollRestoration() {
const router = useRouter()
const retainedComponents = useRef<{ [url: string]: number }>({})
useEffect(() => {
// Save the scroll position of current page before leaving but you can do what you want
const handleRouteChangeStart = (url: string) => {
retainedComponents.current[router.asPath] = window.scrollY
}
// Scroll to the saved position but you can do what you want
const onRouteChangeComplete = (url: string) => {
if (retainedComponents.current[router.asPath]) {
window.scrollTo(0, retainedComponents.current[router.asPath])
}
}
router.events.on('routeChangeStart', handleRouteChangeStart)
router.events.on("routeChangeComplete", onRouteChangeComplete)
return () => {
router.events.off('routeChangeStart', handleRouteChangeStart)
router.events.off("routeChangeComplete", onRouteChangeComplete)
}
}, [router.asPath])
} I've added both hooks in my import useScrollRestoration from "@/utils/hooks/useScrollRestoration";
import useBeforeUnload from "@/utils/hooks/useBeforeUnload";
import type { AppProps } from 'next/app'
// Simplified version of App
export default function App({ Component, pageProps }: AppProps) {
useScrollRestoration()
useBeforeUnload()
return (
<Component {...pageProps} />
)
} It's working very well. I hope it helps 😊 |
Beta Was this translation helpful? Give feedback.
-
Check out this article: https://stackoverflow.com/a/73977517/2190488 The working example also covers:
|
Beta Was this translation helpful? Give feedback.
-
I recommend use router event - routeChangeStart, it will fire when a route starts to change. usage example: 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</>
} |
Beta Was this translation helpful? Give feedback.
-
I get "Error: NextRouter was not mounted." when trying to Does anyone know a solution for my situation? |
Beta Was this translation helpful? Give feedback.
-
Guys some solution for new version? app directory mean |
Beta Was this translation helpful? Give feedback.
-
Need a solution for the new app router. Any idea? |
Beta Was this translation helpful? Give feedback.
-
Following @pimmee's post in other related thread: I would like to share a temporary fix In // This is a temporary fix for `beforeUnload` because router.events do not exist in NextJS v13
useEffect(() => {
const handleAnchorClick = e => {
const targetUrl = e.currentTarget.href, currentUrl = window.location.href;
if (targetUrl !== currentUrl) {
if (window.onbeforeunload) {
const res = window.onbeforeunload();
if (!res) e.preventDefault()
}
}
};
const handleMutation = () => {
const anchorElements = document.querySelectorAll('a[href]');
anchorElements.forEach(anchor => anchor.addEventListener('click', handleAnchorClick));
};
const mutationObserver = new MutationObserver(handleMutation);
mutationObserver.observe(document, { childList: true, subtree: true });
}}, []); In your component: // useBeforeUnload
useEffect(() => {
const beforeUnloadHandler = () => confirm("Changes you made has not been saved just yet. Do you wish to proceed anyway?");
window.onbeforeunload = isUnsaved ? beforeUnloadHandler : null;
return () => window.onbeforeunload = null;
}, [isUnsaved]); The trick is to assign listener via To integrate together with If confirm window shows twice, check if you don't have React's strict mode on, and wrap let didRunOnceInStrictMode = false;
useEffect(() => {
if (!didRunOnceInStrictMode) {
didRunOnceInStrictMode = true;
... It has been working nicely for me so far. |
Beta Was this translation helpful? Give feedback.
-
We have NextJS 13 with a new navigation API and it does not include any feature like this, even more, it removed the only easy way to do that!!! |
Beta Was this translation helpful? Give feedback.
-
Does anybody solved this ( leaving / closing the tab ) using App Router ? I tried everything I found, but nothing seems to work. |
Beta Was this translation helpful? Give feedback.
-
Intercept route changes at the NextJS app router modeDemo:[codeSondbox] cf6e2e9c42a4f29b1dacadffb58c9a1f_723815601830_v_1702122801840414.mp4source code: https://github.com/cgfeel/next.v2/tree/master/routing-file/src/app/leaving/proxy Use this Provider in your layout at the app root directory:https://github.com/cgfeel/next.v2/blob/master/routing-file/src/components/proxyProvider/index.tsx 'use client'
import { usePathname, useSearchParams } from "next/navigation";
import Script from "next/script";
import { FC, PropsWithChildren, createContext, useEffect, useState } from "react";
const ProxyContext = createContext<ProxyInstance>([undefined, () => {}]);
const ProxyProvider: FC<PropsWithChildren<{}>> = ({ children }) => {
const [tips, setTips] = useState<string|undefined>();
const msg = tips === undefined ? tips : (tips||'Are you sure want to leave this page?');
const pathname = usePathname();
const searchParams = useSearchParams();
const url = [pathname, searchParams].filter(i => i).join('?');
useEffect(() => {
setTips(undefined);
}, [url, setTips]);
useEffect(() => {
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
if (msg === undefined) return msg;
event.preventDefault();
event.returnValue = msg;
return msg;
};
const script = document.getElementById('proxy-script');
if (script) {
script.dataset.msg = msg||'';
script.dataset.href = location.href;
}
window.addEventListener("beforeunload", handleBeforeUnload);
return () => {
window.removeEventListener("beforeunload", handleBeforeUnload);
}
}, [msg]);
return (
<ProxyContext.Provider
value={[msg, setTips]}
>
<Script
strategy="afterInteractive"
id="proxy-script"
dangerouslySetInnerHTML={{
__html: `(() => {
const originalPushState = history.pushState.bind(history);
let currentPoint = 0;
let point = 0;
window.history.pushState = function(state, title, url) {
state.point = ++point;
currentPoint = point;
originalPushState(state, title, url);
};
const originalReplaceState = history.replaceState.bind(history);
window.history.replaceState = function(state, title, url) {
state.point = currentPoint;
originalReplaceState(state, title, url);
};
window.addEventListener('popstate', function (event) {
const { state: nextState } = event;
const isback = currentPoint > nextState.point;
currentPoint = nextState.point;
const script = document.getElementById('proxy-script');
if (!script || location.href === script.dataset.href) return;
const msg = script.dataset.msg||'';
const confirm = msg == '' ? true : window.confirm(msg);
if (!confirm) {
event.stopImmediatePropagation();
isback ? history.forward() : history.back();
}
});
})()`,
}}
></Script>
{children}
</ProxyContext.Provider>
);
};
export type ProxyInstance = [
string|undefined, (tips?: string) => void
]
export { ProxyContext };
export default ProxyProvider; |
Beta Was this translation helpful? Give feedback.
-
Hello, router. events didn't exist yet in next.js app dir but you can handle that with some hooks.
But if you want to detect the browser/tab closing events you can use the useBeforeUnload hook via the "react-use" library An example from my code:
cheers ✌ |
Beta Was this translation helpful? Give feedback.
-
@timneutkens How does the Next.js team recommend to show a before-unload warning (i.e. in the context of dirty forms)? |
Beta Was this translation helpful? Give feedback.
-
Feature request
Is your feature request related to a problem? Please describe.
I would like to detect when the user leaves the page. I count 3 ways of leaving a page:
router.back
,router.push
, etc...beforeunload
event is fired)I know this kind of issue has already been opened here or here, but they had been magically closed (using some
duplicate
trick) without providing any solution.Being able to detect when a page is leaved is very helpful for, for example, alerting the user some changes have not been saved yet.
Describe the solution you'd like
I would like something like:
Beta Was this translation helpful? Give feedback.
All reactions