I created this hook to stop warn the user that they have unsaved form changes (React Hook Form). This also works when the user is, say, switching tabs: the code checks for query strings like these: ?tab=vcard. Everything works well…except ?tab=vcard is attached to the URL, so you will see the, say, other tab briefly.
import { useEffect, useState } from 'react';
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
export const useUnsaved = (isDirty: boolean) => {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const [prevQueryString, setPrevQueryString] = useState(() =>
searchParams.toString(),
);
useEffect(() => {
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
if (isDirty) {
event.preventDefault();
}
};
window.addEventListener('beforeunload', handleBeforeUnload);
return () => {
window.removeEventListener('beforeunload', handleBeforeUnload);
};
}, [isDirty]);
useEffect(() => {
const currentQueryString = searchParams.toString();
if (isDirty && currentQueryString !== prevQueryString) {
const confirmLeave = window.confirm(
'You have unsaved changes. Are you sure you want to leave this page?',
);
if (!confirmLeave) {
// Prevent navigation by restoring the previous state
router.push(`${pathname}?${prevQueryString}`);
return; // Stop execution without throwing an error
} else {
setPrevQueryString(currentQueryString); // Update to the new query string
}
}
}, [isDirty, pathname, searchParams, prevQueryString, router]);
useEffect(() => {
const originalPush = router.push;
router.push = async (url: string, ...args: any[]) => {
const currentQueryString = searchParams.toString();
const fullCurrentPath = `${pathname}?${currentQueryString}`;
if (isDirty && url !== fullCurrentPath) {
const confirmLeave = window.confirm(
'You have unsaved changes. Are you sure you want to leave this page?',
);
if (!confirmLeave) {
// Prevent navigation by staying on the current path
router.push(fullCurrentPath);
return; // Just return here, no error thrown
}
}
return originalPush(url, ...args);
};
return () => {
router.push = originalPush;
};
}, [isDirty, pathname, searchParams, router]);
};
So I think I need to stop Next.js from attaching the query string to the URL, and only proceed if the user clicks “Ok.” How can I accomplish this?



