Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | 5x 3x 5x 5x 3x 3x 3x 5x | import { useEffect } from "react";
import { useBlocker } from "react-router-dom";
/**
* Warn the user before they navigate away from a form with unsaved changes.
*
* Covers both navigation kinds:
* - In-app navigation incl. the browser BACK button — via React Router's
* `useBlocker` (requires a data router / RouterProvider, which this app uses).
* A blocked navigation surfaces via the returned blocker so the caller can
* render a confirm dialog (Stay / Leave).
* - Hard navigation (refresh, tab close, address-bar) — via the native
* `beforeunload` prompt.
*
* Implements EI-3115 / EI-TC-4562: "creating an entry and clicking Back" must
* prompt before discarding unsaved data.
*
* LIMITATION: React Router supports only ONE active blocker at a time. Don't
* mount two guarded components simultaneously (e.g. a guarded modal inside a
* guarded page) — the later blocker silently wins. Today's guarded forms live
* on separate routes, so only one is ever mounted.
*
* @param {boolean} when True when the form is dirty (has unsaved changes).
* @returns the React Router blocker (state: "blocked" | "unblocked" | "proceeding",
* with .proceed() and .reset()) — pass it to <UnsavedChangesDialog/>.
*/
export function useUnsavedChangesWarning(when) {
// Block in-app + back-button navigations to a DIFFERENT path while dirty.
const blocker = useBlocker(
({ currentLocation, nextLocation }) =>
Boolean(when) && currentLocation.pathname !== nextLocation.pathname
);
// Native guard for refresh / tab-close / external navigation.
useEffect(() => {
if (!when) return undefined;
const handleBeforeUnload = (event) => {
event.preventDefault();
// Chrome requires returnValue to be set to trigger the prompt.
event.returnValue = "";
return "";
};
window.addEventListener("beforeunload", handleBeforeUnload);
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
}, [when]);
return blocker;
}
export default useUnsavedChangesWarning;
|