Skip to content

useBeforeUnload

Edit

Requests the browser's standard leave-page confirmation when unsaved work should block navigation.

useBeforeUnload.ts
import { useEffect, useLayoutEffect, useRef } from "react";
export type BeforeUnloadPredicate = (this: void) => boolean;
const useIsomorphicLayoutEffect =
typeof window === "undefined" ? useEffect : useLayoutEffect;
/**
* Requests the browser's standard leave-page confirmation when enabled.
*/
export const useBeforeUnload = (
shouldBlock: boolean | BeforeUnloadPredicate = true,
): void => {
const shouldBlockRef = useRef(shouldBlock);
const shouldSubscribe = typeof shouldBlock === "function" || shouldBlock;
useIsomorphicLayoutEffect(() => {
shouldBlockRef.current = shouldBlock;
}, [shouldBlock]);
useIsomorphicLayoutEffect(() => {
if (typeof window === "undefined" || !shouldSubscribe) return;
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
const block =
typeof shouldBlockRef.current === "function"
? Reflect.apply(shouldBlockRef.current, undefined, [])
: shouldBlockRef.current;
if (!block) return;
event.preventDefault();
event.returnValue = "";
};
window.addEventListener("beforeunload", handleBeforeUnload);
return () => {
window.removeEventListener("beforeunload", handleBeforeUnload);
};
}, [shouldSubscribe]);
};
Terminal
wget -O src/hooks/useBeforeUnload.ts https://raw.githubusercontent.com/jrTilak/lazykit/HEAD/registry/react-hooks/useBeforeUnload.ts
useBeforeUnload.example.tsx
import { useState } from "react";
import { useBeforeUnload } from "./useBeforeUnload";
export const UnsavedDraft = () => {
const [draft, setDraft] = useState("");
const [savedDraft, setSavedDraft] = useState("");
useBeforeUnload(draft !== savedDraft);
return (
<section>
<textarea
value={draft}
onChange={(event) => setDraft(event.currentTarget.value)}
/>
<button type="button" onClick={() => setSavedDraft(draft)}>
Save
</button>
</section>
);
};
  • shouldBlock (boolean | (() => boolean), default: true) — Boolean or latest predicate deciding whether to prevent the beforeunload event. false installs no listener; a predicate is evaluated for each event.

void. The listener is removed on unmount and is never installed during server rendering. Browsers control the confirmation text; custom messages are intentionally unsupported.