Detect when user leaves the page #9662
Replies: 37 comments 44 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.
-
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.
-
@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.
-
"use client";
import { useRouter } from "next/navigation";
import { memo, useEffect, useRef, useState } from "react";
import { LeavingDialog } from "../components/ui/LeavingDialog";
type PreventNavigationProps = {
isDirty: boolean;
backHref: string;
resetData: () => void;
};
export const PreventNavigation = ({
isDirty,
backHref,
resetData,
}: PreventNavigationProps) => {
const [leavingPage, setLeavingPage] = useState(false);
const router = useRouter();
/**
* Function that will be called when the user selects `yes` in the confirmation modal,
* redirected to the selected page.
*/
const confirmationFn = useRef<() => void>(() => {});
// Used to make popstate event trigger when back button is clicked.
// Without this, the popstate event will not fire because it needs there to be a href to return.
useEffect(() => {
if (typeof window !== "undefined") {
window.history.pushState(null, document.title, window.location.href);
}
}, []);
useEffect(() => {
/**
* Used to prevent navigation when user clicks a navigation `<Link />` or `<a />`.
* @param e The triggered event.
*/
let lastClickTime = 0;
const debounceTime = 300; // milliseconds
const handleClick = (event: MouseEvent) => {
const now = Date.now();
if (now - lastClickTime < debounceTime) return;
lastClickTime = now;
console.log("i handled it ");
let target = event.target as HTMLElement;
while (target && target.tagName !== "A") {
target = target.parentElement as HTMLElement;
}
if (target && target.tagName === "A") {
target = target as HTMLAnchorElement;
if (isDirty && typeof window !== "undefined"&&target.href !== window.location.href) {
console.log("i checked ");
window.history.pushState(null, document.title, window.location.href);
confirmationFn.current = () => {
router.push(backHref);
};
setLeavingPage(true);
}
}
};
/**
* Used to prevent navigation when using `back` browser buttons.
*/
const handlePopState = () => {
if (isDirty&&typeof window !== "undefined") {
window.history.pushState(null, document.title, window.location.href);
confirmationFn.current = () => {
router.push(backHref);
};
setLeavingPage(true);
} else {
if(typeof window !== "undefined")
window.history.back();
}
};
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
if (isDirty) {
e.preventDefault();
e.returnValue = "";
}
};
if(typeof window !== "undefined"){
/* *************************** Open listeners ************************** */
document.addEventListener("click", handleClick);
window.addEventListener("popstate", handlePopState);
window.addEventListener("beforeunload", handleBeforeUnload);
}
/* ************** Return from useEffect closing listeners ************** */
return () => {
if(typeof window !== "undefined"){
document.removeEventListener("click", handleClick);
window.removeEventListener("popstate", handlePopState);
window.removeEventListener("beforeunload", handleBeforeUnload);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
},[isDirty]);
return (
<>
<LeavingDialog
isOpen={leavingPage}
noCallback={() => {
setLeavingPage(false);
confirmationFn.current = () => {};
}}
yesCallback={() => {
confirmationFn.current();
setLeavingPage(false);
confirmationFn.current = () => {};
resetData();
}}
/>
</>
);
}; This worked for me just add the component in whatever page you want to prevent navigation. import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
type LeavingDialogProps = {
isOpen: boolean;
yesCallback: () => void;
noCallback: () => void;
};
export const LeavingDialog = ({
isOpen,
yesCallback,
noCallback,
}: LeavingDialogProps) => {
return (
<AlertDialog open={isOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>The data will be lost.</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to leave the page?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={() => noCallback()}>No</AlertDialogCancel>
<AlertDialogAction className=" text-white" onClick={() => yesCallback()}>
Yes
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}; |
Beta Was this translation helpful? Give feedback.
-
I'm using the code below and its working well except for when i press the back button the alert pops up, the page doesn't navigate but the url changes. So if the user clicks cancel, the url is actually wrong. Is there anyway I can prevent the url to only change when the user confirms? export const useRouteChangeConfirm = (unsavedChanges: boolean) => {
} |
Beta Was this translation helpful? Give feedback.
-
This is insane that we are about to get v15 soon and yet such needed functionality is not baked in still ? Like come on.. is it a joke NextJs team ?? |
Beta Was this translation helpful? Give feedback.
-
I find a solution (not a great one) but a solution that works with App router:
And thank you to ChatGpt for the initial help :) |
Beta Was this translation helpful? Give feedback.
-
I have the same issue. This feature is super important when working with forms. I hope this gets included soon. |
Beta Was this translation helpful? Give feedback.
-
I am trying to introduce pop-ups to retain users attempting to leave my site. |
Beta Was this translation helpful? Give feedback.
-
Finally I created a library for showing page leave confirmation in Next.js . https://github.com/LayerXcom/next-navigation-guard It works with both App Router and Pages Router. How it works is described here: |
Beta Was this translation helpful? Give feedback.
-
This feature would be nice. |
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