How to prevent leaving the current page when the user manually changes the URL in the browser's address bar? #50700
Replies: 4 comments 6 replies
-
For future reference this is what I ended up with const useLeavePageConfirm = (active = true) => {
const beforeUnloadListener = (event: any) => {
event.preventDefault()
return (event.returnValue = "")
}
React.useEffect(() => {
if (active) {
addEventListener("beforeunload", beforeUnloadListener)
} else {
removeEventListener("beforeunload", beforeUnloadListener)
}
}, [active])
}
export default useLeavePageConfirm |
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.
-
Preventing form page exit in Nextjs 14I managed to come up with a model that addresses three of these possibilities: Now our Custom hook that will prevent navigation: import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { NavigateOptions } from 'next/dist/shared/lib/app-router-context.shared-runtime';
export const useWarnIfUnsavedChanges = (unsaved: boolean) => {
const router = useRouter();
const handleAnchorClick = (e: MouseEvent) => {
if (e.button !== 0) return; // only handle left-clicks
const targetUrl = (e.currentTarget as HTMLAnchorElement).href;
const currentUrl = window.location.href;
if (targetUrl !== currentUrl && window.onbeforeunload) {
// @ts-ignore
const res = window.onbeforeunload();
if (!res) e.preventDefault();
}
};
const addAnchorListeners = () => {
const anchorElements = document.querySelectorAll('a[href]');
anchorElements.forEach((anchor) => anchor.addEventListener('click', handleAnchorClick));
};
useEffect(() => {
const mutationObserver = new MutationObserver(addAnchorListeners);
mutationObserver.observe(document.body, { childList: true, subtree: true });
addAnchorListeners();
return () => {
mutationObserver.disconnect();
const anchorElements = document.querySelectorAll('a[href]');
anchorElements.forEach((anchor) => anchor.removeEventListener('click', handleAnchorClick));
};
}, []);
useEffect(() => {
const beforeUnloadHandler = (e: BeforeUnloadEvent) => {
e.preventDefault();
e.returnValue = ''; // required for Chrome
};
const handlePopState = (e: PopStateEvent) => {
if (unsaved) {
const confirmLeave = window.confirm(
'You have unsaved changes. Are you sure you want to leave?',
);
if (!confirmLeave) {
e.preventDefault();
window.history.pushState(null, '', window.location.href);
}
}
};
if (unsaved) {
window.addEventListener('beforeunload', beforeUnloadHandler);
window.addEventListener('popstate', handlePopState);
} else {
window.removeEventListener('beforeunload', beforeUnloadHandler);
window.removeEventListener('popstate', handlePopState);
}
return () => {
window.removeEventListener('beforeunload', beforeUnloadHandler);
window.removeEventListener('popstate', handlePopState);
};
}, [unsaved]);
useEffect(() => {
const originalPush = router.push;
router.push = (url: string, options?: NavigateOptions) => {
if (unsaved) {
const confirmLeave = window.confirm(
'You have unsaved changes. Are you sure you want to leave?',
);
if (confirmLeave) originalPush(url, options);
} else {
originalPush(url, options);
}
};
return () => {
router.push = originalPush;
};
}, [router, unsaved]);
}; Now just pass this component along with our form, filling in the necessary properties: useWarnIfUnsavedChanges(isDirty); |
Beta Was this translation helpful? Give feedback.
-
To avoid losing unsaved data, I need to prevent exiting the current page when the user manually changes the URL in the browser's address bar. I'm trying to use the "routeChangeStart" event, which only works fine for switching routes using NextJS tools like Link, router.push, etc. I'm using the "popstate" event, which only works with the browser's back and forward buttons. Could you suggest what event I can use to handle the URL change manually?
Beta Was this translation helpful? Give feedback.
All reactions