Replies: 74 comments 68 replies
-
If someone needs this feature I found that better workaround is just to throw an error in Router.ready(() => {
Router.router.on('routeChangeStart', () => {
throw new Error('Abort');
});
}); |
Beta Was this translation helpful? Give feedback.
-
Seems like a reasonable solution would be to abort the routing if the return value from |
Beta Was this translation helpful? Give feedback.
-
The workarounds above no longer work in Next.js 4, I believe this is because there are no methods exposed on the router itself that would give us control over this. Particularly, there is no way to expose componentLoadCancel to manually cancel a route A use-case where this would be helpful is "unsaved changes" dialogs/warnings. I'm open to making a PR if this is something that would be desired. |
Beta Was this translation helpful? Give feedback.
-
The workarounds only work for a link clicked but don't work when using browser navigation for instance. I agree with @erikras, that would be really nice to be able to return a boolean in onRouteChangeStart to cancel the route change! Router.onRouteChangeStart = (url) => {
if (!window.confirm('You really want to leave?')) {
return false;
}
return true;
} Unfortunately, onRouteChangeStart is just an event and can't manage any value to return. |
Beta Was this translation helpful? Give feedback.
-
I think it makes sense to cancel route by throwing a custom error. Router.onRouteChangeStart = (url) => {
if (!window.confirm('You really want to leave?')) {
throw new Router.Abort();
}
} What do you think? |
Beta Was this translation helpful? Give feedback.
-
Both work for me as long as we have this feature which is really useful. I think It's more common to manage this with a boolean but more understandable with this |
Beta Was this translation helpful? Give feedback.
-
Did anything like this make it into 5.0? Anybody done something like this in 5.0 yet? |
Beta Was this translation helpful? Give feedback.
-
Also looking for this feature. Working on a editor app and need to be able to cancel route changes in case there are unsaved changes. |
Beta Was this translation helpful? Give feedback.
-
The only way to work with this was using
|
Beta Was this translation helpful? Give feedback.
-
@Negan1911 , where do you catch the raised error? |
Beta Was this translation helpful? Give feedback.
-
@justbrody no need to catch it, it will appear on the console but prevent the navigation |
Beta Was this translation helpful? Give feedback.
-
@Negan1911 , Thanks for the quick reply. I'm trying not to polute the console, people will get nervous seeing errors in the console. |
Beta Was this translation helpful? Give feedback.
-
@justbrody Yeah i know, my QA got crazy because of that, but nothing to do at this level ¯_(ツ)_/¯¯ |
Beta Was this translation helpful? Give feedback.
-
@Negan1911 I'm going to wrap the Router calls with my own implementation for more control. |
Beta Was this translation helpful? Give feedback.
-
I just ended up dumping work-in-progress stuff to |
Beta Was this translation helpful? Give feedback.
-
This is a js solution built on top of various contributors (mainly @ryym, @GorkemSahin and @dkershner6 and more) It supports
import Router, { useRouter } from "next/router"
import { useRef, useEffect, useCallback } from "react"
const throwFakeErrorToFoolNextRouter = () => {
// Throwing an actual error class trips the Next.JS 500 Page, this string literal does not.
throw "Abort route change due to unsaved changes in form. Triggered by useWarningOnExit. Please ignore this error. See issue https://github.com/vercel/next.js/issues/2476"
}
/** @returns {Window?} */
const getWindow = () => typeof window !== "undefined" && window
/** @returns {History.state?} */
const getHistory = () => getWindow()?.history?.state
/**
* @param {boolean} shouldWarn
* @param {string?} message
*/
export const useWarningOnExit = (shouldWarn = true, message = "Discard unsaved changes?") => {
const router = useRouter()
const lastHistory = useRef(getHistory())
useEffect(() => {
const storeLastHistoryState = () => {
lastHistory.current = getHistory()
}
router.events.on("routeChangeComplete", storeLastHistoryState)
return () => {
router.events.off("routeChangeComplete", storeLastHistoryState)
}
}, [router])
/**
* @experimental HACK - idx is not documented
* Determines which direction to travel in history.
*/
const revertTheChangeRouterJustMade = useCallback(() => {
const state = lastHistory.current
if (
state !== null &&
history.state !== null &&
state.idx !== history.state.idx
) {
const delta = lastHistory.current.idx < history.state.idx ? -1 : 1
history.go(delta)
}
}, [])
const killRouterEvent = useCallback(() => {
router.events.emit("routeChangeError")
revertTheChangeRouterJustMade()
throwFakeErrorToFoolNextRouter()
}, [revertTheChangeRouterJustMade, router])
useEffect(() => {
let isWarned = false
const routeChangeStart = (url) => {
if (router.asPath !== url && shouldWarn && !isWarned) {
isWarned = true
if (window.confirm(message)) {
router.push(url)
return
}
isWarned = false
killRouterEvent()
}
}
const beforeUnload = (e) => {
if (shouldWarn && !isWarned) {
const event = e ?? getWindow()?.event
event?.returnValue = message
return message
}
return null
}
router.events.on("routeChangeStart", routeChangeStart)
getWindow()?.addEventListener("beforeunload", beforeUnload)
return () => {
router.events.off("routeChangeStart", routeChangeStart)
getWindow()?.removeEventListener("beforeunload", beforeUnload)
}
}, [message, shouldWarn, killRouterEvent, router])
}
export default useWarningOnExit Example with next.js and react-hook-form useWarningOnExit(formState.isDirty) |
Beta Was this translation helpful? Give feedback.
-
Here is a working version for TypeScript if anyone needs it. Testing in Chrome, Safari, Opera. import { useRouter } from 'next/router';
import { useCallback, useEffect, useRef } from 'react';
import { UrlObject } from 'url';
const throwFakeErrorToFoolNextRouter = () => {
// Throwing an actual error class trips the Next.JS 500 Page, this string literal does not.
// eslint-disable-next-line @typescript-eslint/no-throw-literal
throw 'Abort route change due to unsaved changes in form. Triggered by useWarningOnExit. Please ignore this error. See issue https://github.com/vercel/next.js/issues/2476';
};
const getWindow = (): Window | null => (typeof window !== 'undefined' ? window : null);
const getHistory = () => getWindow()?.history?.state;
export const useWarningOnExit = (shouldWarn = true, message = 'Discard unsaved changes?') => {
const router = useRouter();
const lastHistory = useRef(getHistory());
useEffect(() => {
const storeLastHistoryState = () => {
lastHistory.current = getHistory();
};
router.events.on('routeChangeComplete', storeLastHistoryState);
return () => {
router.events.off('routeChangeComplete', storeLastHistoryState);
};
}, [router]);
/**
* @experimental HACK - idx is not documented
* Determines which direction to travel in history.
*/
const revertTheChangeRouterJustMade = useCallback(() => {
const state = lastHistory.current;
if (state !== null && history.state !== null && state.idx !== history.state.idx) {
const delta = lastHistory.current.idx < history.state.idx ? -1 : 1;
history.go(delta);
}
}, []);
const killRouterEvent = useCallback(() => {
router.events.emit('routeChangeError');
revertTheChangeRouterJustMade();
throwFakeErrorToFoolNextRouter();
}, [revertTheChangeRouterJustMade, router]);
useEffect(() => {
let isWarned = false;
const routeChangeStart = (url: string | UrlObject) => {
if (router.asPath !== url && shouldWarn && !isWarned) {
isWarned = true;
if (window.confirm(message)) {
router.push(url);
return;
}
isWarned = false;
killRouterEvent();
}
};
const beforeUnload = (e: Event | null) => {
if (shouldWarn && !isWarned) {
const event = e ?? getWindow()?.event;
if (event) {
event.returnValue = Boolean(message);
}
return message;
}
return null;
};
router.events.on('routeChangeStart', routeChangeStart);
getWindow()?.addEventListener('beforeunload', beforeUnload);
return () => {
router.events.off('routeChangeStart', routeChangeStart);
getWindow()?.removeEventListener('beforeunload', beforeUnload);
};
}, [message, shouldWarn, killRouterEvent, router]);
};
export default useWarningOnExit; |
Beta Was this translation helpful? Give feedback.
-
Hi, This hook allows me to use a custom modal dialog (not window.confirm) to ask user to confirm whether to proceed or prevent the navigation. I am not handling the beforeunload event on purpose as I don't think we should handle that scenario. Usage example: // let's assume formDirty is used to toggle the custom hook
const navigate = useNavigationObserver({
shouldStopNavigation: formDirty,
onNavigate: () => {
setShowModal(true)
},
}) The hook requires a boolean and a callback. The former is to toggle navigation interception on/off while the latter is called when a navigation attempt has been made. In my case I show a custom modal by using It returns a method that, if executed, triggers the navigation. the code: import { useRouter } from 'next/router'
import { useCallback, useEffect, useRef } from 'react'
const throwFakeErrorToFoolNextRouter = () => {
// Throwing an actual error class trips the Next.JS 500 Page, this string literal does not.
// eslint-disable-next-line no-throw-literal
throw ' 👍 Abort route change due to unsaved changes in form. Triggered by useNavigationObserver. Please ignore this error.'
}
interface Props {
shouldStopNavigation: boolean
onNavigate: (nextUrl: string) => void
}
const useNavigationObserver = ({ shouldStopNavigation, onNavigate }: Props) => {
const router = useRouter()
const currentPath = router.asPath
const nextPath = useRef('')
const killRouterEvent = useCallback(() => {
router.events.emit('routeChangeError')
throwFakeErrorToFoolNextRouter()
}, [router])
useEffect(() => {
const onRouteChange = (url: string) => {
if (shouldStopNavigation && url !== currentPath) {
nextPath.current = url
onNavigate(url)
killRouterEvent()
}
}
router.events.on('routeChangeStart', onRouteChange)
return () => {
router.events.off('routeChangeStart', onRouteChange)
}
}, [
currentPath,
killRouterEvent,
onNavigate,
router.events,
shouldStopNavigation,
])
const navigate = () => {
router.push(nextPath.current)
}
return navigate
}
export { useNavigationObserver } |
Beta Was this translation helpful? Give feedback.
-
import { useRouter } from "next/router";
import { useCallback, useEffect, useState } from "react";
const useBeforeUnload = (enabled: boolean) => {
const handler = useCallback(
(event: BeforeUnloadEvent) => {
if (!enabled) {
return;
}
event.preventDefault();
},
[enabled]
);
useEffect(() => {
if (!enabled) {
return;
}
window.addEventListener("beforeunload", handler);
return () => window.removeEventListener("beforeunload", handler);
}, [enabled, handler]);
};
export const useFormLeave = (isDirty: boolean) => {
const router = useRouter();
const [showLeaveModal, setShowLeaveModal] = useState(false);
const [pathname, setPathname] = useState<string | null>(null);
useBeforeUnload(isDirty);
const onRouteChangeStart = useCallback(
(pathname: string) => {
if (!isDirty) {
return;
}
setShowLeaveModal(true);
setPathname(pathname);
throw "\nRoute change aborted. Please ignore this error";
},
[isDirty]
);
const removeRouteChangeStart = useCallback(() => router.events.off("routeChangeStart", onRouteChangeStart), [router.events, onRouteChangeStart]);
const handleUserChoice = useCallback(
(leave: boolean) => async () => {
setShowLeaveModal(false);
if (!leave) {
setPathname(null);
return;
}
removeRouteChangeStart();
if (pathname) {
await router.push(pathname);
}
},
[pathname, removeRouteChangeStart, router]
);
useEffect(() => {
router.events.on("routeChangeStart", onRouteChangeStart);
return removeRouteChangeStart;
}, [onRouteChangeStart, removeRouteChangeStart, router.events]);
return [showLeaveModal, handleUserChoice] as const;
}; |
Beta Was this translation helpful? Give feedback.
-
From my point for view the most critical point is using As earlier comments said not only users but also QAs concern a lot while receiving errors in the console. |
Beta Was this translation helpful? Give feedback.
-
Sharing my version in case it helps anyone. import useTranslation from 'next-translate/useTranslation'
import { useRouter } from 'next/router'
import { useEffect, useRef } from 'react'
export function useExitConfirmation({
message,
enable,
}: {
message?: string
enable: boolean
}) {
const { t } = useTranslation()
const router = useRouter()
const bypassConfirmationRef = useRef(false)
message =
message ??
t(
'Your changes have not been saved. Are you sure you want to leave this page?',
)
useEffect(() => {
const shouldByPassconfimation = () =>
!enable || bypassConfirmationRef.current
const handleWindowClose = (e: BeforeUnloadEvent) => {
if (shouldByPassconfimation()) return
e.preventDefault()
return (e.returnValue = message)
}
const handleBrowseAway = () => {
if (shouldByPassconfimation()) return
if (window.confirm(message)) return
router.events.emit('routeChangeError')
throw 'routeChange aborted by user.'
}
window.addEventListener('beforeunload', handleWindowClose)
router.events.on('routeChangeStart', handleBrowseAway)
return () => {
window.removeEventListener('beforeunload', handleWindowClose)
router.events.off('routeChangeStart', handleBrowseAway)
}
}, [enable, router.events, t, message])
return {
bypassExitConfirmation(value = true) {
bypassConfirmationRef.current = value
},
}
} |
Beta Was this translation helpful? Give feedback.
-
Isn't this important enough? It is such a common usecase.. Can someone please take it from the Next team? Help us NextJS team 🙏 |
Beta Was this translation helpful? Give feedback.
-
If someone from Nextjs's team sees it, I'm willing to pay for this feature to be implemented, it's such a common usecase 🙄 |
Beta Was this translation helpful? Give feedback.
-
Edit: got this to work. Note that The confusion was that eslint was suggesting I throw a So the complete three lines are: Router.events.emit('routeChangeError')
// eslint-disable-next-line no-throw-literal
throw "Abort route change by user's confirmation." |
Beta Was this translation helpful? Give feedback.
-
This is my file, Hope it can help you :D import { useCallback, useEffect, useMemo, useState } from 'react';
import { useRouter } from 'next/router';
type Props = {
message?: string;
};
const defaultMessage = '';
function useLeaveConfirm({ message = '' }: Props = {}) {
const Router = useRouter();
const [isDirty, setIsDirty] = useState(false);
const finalMSG = useMemo(() => message || defaultMessage, [message]);
const onRouteChangeStart = useCallback(() => {
if (isDirty) {
// eslint-disable-next-line no-alert
if (window.confirm(finalMSG)) {
return true;
}
// eslint-disable-next-line @typescript-eslint/no-throw-literal
throw "Abort route change by user's confirmation.";
}
return null;
}, [isDirty, finalMSG]);
useEffect(() => {
Router.events.on('routeChangeStart', onRouteChangeStart);
return () => {
Router.events.off('routeChangeStart', onRouteChangeStart);
};
}, [Router.events, onRouteChangeStart]);
useEffect(() => {
const handleBeforeUnload = () => {
if (isDirty) {
return true;
}
return undefined;
};
if (typeof window !== 'undefined') {
window.onbeforeunload = handleBeforeUnload;
}
return () => {
if (typeof window !== 'undefined') {
window.onbeforeunload = null;
}
};
}, [isDirty]);
return { isDirty, setIsDirty };
}
export default useLeaveConfirm; How to use: const { setIsDirty } = useLeaveConfirm();
const onChange = () => {
setIsDirty(true);
// your code
}
const onSubmit = () => {
submit().then(() => {
setIsDirty(false);
})
} |
Beta Was this translation helpful? Give feedback.
-
Got this to work! (as of June 21, 2023) Here's a quick You can find the complete gist right here: But here's the relevant code: // make sure you import Router and useEffect
import Router from "next/router";
import { useEffect } from 'react';
// then in your component, simply add this useEffect:
const shouldBlockNavigation = true; // set this up however you want
useEffect(() => {
const nativeBrowserHandler = (event) => {
if (shouldBlockNavigation) {
event.preventDefault();
}
};
const nextNavigationHandler = (url) => {
if (shouldBlockNavigation) {
if (!window.confirm('Navigate away? Changes you made may not be saved.')) {
Router.events.emit('routeChangeError')
// eslint-disable-next-line no-throw-literal
throw "Abort route change by user's confirmation."
}
}
};
window.addEventListener('beforeunload', nativeBrowserHandler);
Router.events.on("beforeHistoryChange", nextNavigationHandler);
// On component unmount, remove the event listener and finish the upload
return () => {
window.removeEventListener('beforeunload', nativeBrowserHandler);
Router.events.off("beforeHistoryChange", nextNavigationHandler);
};
}, [shouldBlockNavigation]); |
Beta Was this translation helpful? Give feedback.
-
Hi all,
My solution is based on this old response, it adds support for the browser back/forward buttons, the router basePath and doesn't pollute the console with error logs It also implements this great suggestion to remove errors in the console. the custom hook: import { useRouter } from 'next/router'
import { useCallback, useEffect, useRef } from 'react'
const errorMessage = 'Please ignore this error.'
const throwFakeErrorToFoolNextRouter = () => {
// Throwing an actual error class trips the Next.JS 500 Page, this string literal does not.
// eslint-disable-next-line no-throw-literal
throw errorMessage
}
const rejectionHandler = (event: PromiseRejectionEvent) => {
if (event.reason === errorMessage) {
event.preventDefault()
}
}
interface Props {
shouldStopNavigation: boolean
onNavigate: () => void
}
const useNavigationObserver = ({ shouldStopNavigation, onNavigate }: Props) => {
const router = useRouter()
const currentPath = router.asPath
const nextPath = useRef('')
const navigationConfirmed = useRef(false)
const killRouterEvent = useCallback(() => {
router.events.emit('routeChangeError', '', '', { shallow: false })
throwFakeErrorToFoolNextRouter()
}, [router])
useEffect(() => {
navigationConfirmed.current = false
const onRouteChange = (url: string) => {
if (currentPath !== url) {
// if the user clicked on the browser back button then the url displayed in the browser gets incorrectly updated
// This is needed to restore the correct url.
// note: history.pushState does not trigger a page reload
window.history.pushState(null, '', router.basePath + currentPath)
}
if (
shouldStopNavigation &&
url !== currentPath &&
!navigationConfirmed.current
) {
// removing the basePath from the url as it will be added by the router
nextPath.current = url.replace(router.basePath, '')
onNavigate()
killRouterEvent()
}
}
router.events.on('routeChangeStart', onRouteChange)
window.addEventListener('unhandledrejection', rejectionHandler)
return () => {
router.events.off('routeChangeStart', onRouteChange)
window.removeEventListener('unhandledrejection', rejectionHandler)
}
}, [
currentPath,
killRouterEvent,
onNavigate,
router.basePath,
router.events,
shouldStopNavigation,
])
const confirmNavigation = () => {
navigationConfirmed.current = true
router.push(nextPath.current)
}
return confirmNavigation
}
export { useNavigationObserver } Usage example: // let's assume formDirty is used to toggle the custom hook
const navigate = useNavigationObserver({
shouldStopNavigation: formDirty,
onNavigate: () => {
setShowModal(true)
},
})
...
// somewhere else if the user confirms s/he wants to leave
navigate(); |
Beta Was this translation helpful? Give feedback.
-
someone has a fix with app directory? 😅 |
Beta Was this translation helpful? Give feedback.
-
The below code solves AppRouter route cancellation app/ dirNOT for pages router /pages dir I got a fix for app directory AppRouter. works like a charm. Posting here for everyone in need.
/// useBeforeUnload.ts
'use client';
import { useRouter } from 'next/navigation';
import { useEffect, useId } from 'react';
import { remove } from 'lodash-es';
let isForceRouting = false;
const activeIds: string[] = [];
let lastKnownHref: string;
export const useBeforeUnload = (isActive = true) => {
const id = useId();
// Handle <Link> clicks & onbeforeunload(attemptimg to close/refresh browser)
useEffect(() => {
if (!isActive) return;
lastKnownHref = window.location.href;
activeIds.push(id);
const handleAnchorClick = (e: Event) => {
const targetUrl = (e.currentTarget as HTMLAnchorElement).href,
currentUrl = window.location.href;
if (targetUrl !== currentUrl) {
const res = beforeUnloadFn();
if (!res) e.preventDefault();
lastKnownHref = window.location.href;
}
};
let anchorElements: HTMLAnchorElement[] = [];
const disconnectAnchors = () => {
anchorElements.forEach((anchor) => {
anchor.removeEventListener('click', handleAnchorClick);
});
};
const handleMutation = () => {
disconnectAnchors();
anchorElements = Array.from(document.querySelectorAll('a[href]'));
anchorElements.forEach((anchor) => {
anchor.addEventListener('click', handleAnchorClick);
});
};
const mutationObserver = new MutationObserver(handleMutation);
mutationObserver.observe(document.body, { childList: true, subtree: true });
addEventListener('beforeunload', beforeUnloadFn);
return () => {
removeEventListener('beforeunload', beforeUnloadFn);
disconnectAnchors();
mutationObserver.disconnect();
remove(activeIds, (x) => x === id);
};
}, [isActive, id]);
};
const beforeUnloadFn = (event?: BeforeUnloadEvent) => {
if (isForceRouting) return true;
const message = 'Discard unsaved changes?';
if (event) {
event.returnValue = message;
return message;
} else {
return confirm(message);
}
};
const BeforeUnloadProvider = ({ children }: React.PropsWithChildren) => {
const router = useRouter();
useEffect(() => {
lastKnownHref = window.location.href;
});
// Hack nextjs13 popstate impl, so it will include route cancellation.
// This Provider has to be rendered in the layout phase wrapping the page.
useEffect(() => {
let nextjsPopStateHandler: (...args: any[]) => void;
function popStateHandler(...args: any[]) {
useBeforeUnload.ensureSafeNavigation(
() => {
nextjsPopStateHandler(...args);
lastKnownHref = window.location.href;
},
() => {
router.replace(lastKnownHref, { scroll: false });
}
);
}
addEventListener('popstate', popStateHandler);
const originalAddEventListener = window.addEventListener;
window.addEventListener = (...args: any[]) => {
if (args[0] === 'popstate') {
nextjsPopStateHandler = args[1];
window.addEventListener = originalAddEventListener;
} else {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
originalAddEventListener(...args);
}
};
return () => {
window.addEventListener = originalAddEventListener;
removeEventListener('popstate', popStateHandler);
};
}, []);
return children;
};
useBeforeUnload.Provider = BeforeUnloadProvider;
useBeforeUnload.forceRoute = async (cb: () => void | Promise<void>) => {
try {
isForceRouting = true;
await cb();
} finally {
isForceRouting = false;
}
};
useBeforeUnload.ensureSafeNavigation = (
onPerformRoute: () => void,
onRouteRejected?: () => void
) => {
if (activeIds.length === 0 || beforeUnloadFn()) {
onPerformRoute();
} else {
onRouteRejected?.();
}
}; /// layout.tsx (root layout)
export default function RootLayout({children}: React.PropsWithChildren) {
return <useBeforeUnload.Provider>{children}</useBeforeUnload.Provider>
} ///SomeFileWithRouteProtection.tsx
// in the render phase somewhere
useBeforeUnload(isFormDirty);
// Example of forceRouting
function saveAndReroute() {
useBeforeUnload.forceRoute(async () => {
await save() // If we save and reroute inside save(), we should be able to bypass the route cancellation
});
} Enjoy |
Beta Was this translation helpful? Give feedback.
-
Solved for app/ dir, maybeHi, everyone. I think the code below is simple and robust fix. /// useBeforeUnload.ts
import { useEffect } from 'react'
import { useBeforeUnload as _useBeforeUnload } from 'react-use'
export const useBeforeUnload = (
isConfirm = true,
message = 'Are you sure want to leave this page?'
) => {
// check when page is about to be reloaded
_useBeforeUnload(isConfirm, message)
// check when page is about to be changed
useEffect(() => {
function isAnchorOfCurrentUrl(currentUrl: string, newUrl: string) {
const currentUrlObj = new URL(currentUrl)
const newUrlObj = new URL(newUrl)
// Compare hostname, pathname, and search parameters
if (
currentUrlObj.hostname === newUrlObj.hostname &&
currentUrlObj.pathname === newUrlObj.pathname &&
currentUrlObj.search === newUrlObj.search
) {
// Check if the new URL is just an anchor of the current URL page
const currentHash = currentUrlObj.hash
const newHash = newUrlObj.hash
return (
currentHash !== newHash &&
currentUrlObj.href.replace(currentHash, '') === newUrlObj.href.replace(newHash, '')
)
}
return false
}
function findClosestAnchor(element: HTMLElement | null): HTMLAnchorElement | null {
while (element && element.tagName.toLowerCase() !== 'a') {
element = element.parentElement
}
return element as HTMLAnchorElement
}
function handleClick(event: MouseEvent) {
try {
const target = event.target as HTMLElement
const anchor = findClosestAnchor(target)
if (anchor) {
const currentUrl = window.location.href
const newUrl = (anchor as HTMLAnchorElement).href
const isAnchor = isAnchorOfCurrentUrl(currentUrl, newUrl)
const isDownloadLink = (anchor as HTMLAnchorElement).download !== ''
const isPageLeaving = !(newUrl === currentUrl || isAnchor || isDownloadLink)
if (isPageLeaving && isConfirm && !window.confirm(message)) {
// Cancel the route change
event.preventDefault()
event.stopPropagation()
}
}
} catch (err) {
alert(err)
}
}
// Add the global click event listener
document.addEventListener('click', handleClick, true)
// Clean up the global click event listener when the component is unmounted
return () => {
document.removeEventListener('click', handleClick, true)
}
}, [isConfirm, message])
} How to use/// YourComponent.tsx
import {useBeforeUnload} from "@/hooks/useBeforeUnload"
function YourComponent () {
useBeforeUnload(isFormDirty, customMsg)
return <>...</>
} |
Beta Was this translation helpful? Give feedback.
-
Expected Behavior
A method in router API that allows aborting upcoming route change.
Current Behavior
Right now router exposes
abortComponentLoad
method but it seems to be designed only for internal use and it doesn't work if it's called inrouteChangeStart
handler. It works only after this piece of code was executed:Workaround:
Context
This is useful for example when building complex forms and we want to warn a user before he leaves the page without submitting.
Your Environment
ANY
Beta Was this translation helpful? Give feedback.
All reactions