React cookbook · Intermediate
Warn users about unsaved changes
Prevent users from losing a half-filled form when navigating away or closing the tab.
Approach
Track dirtiness (e.g., React Hook Form's isDirty). Block in-app navigation with React Router's useBlocker and show a confirmation, and handle tab close/reload with the beforeunload event (browsers show their own generic message).
Solution
import { useEffect } from "react";
import { useBlocker } from "react-router";
export function useUnsavedChangesWarning(isDirty: boolean) {
const blocker = useBlocker(({ currentLocation, nextLocation }) =>
isDirty && currentLocation.pathname !== nextLocation.pathname,
);
useEffect(() => {
if (!isDirty) return;
const handler = (e: BeforeUnloadEvent) => { e.preventDefault(); };
window.addEventListener("beforeunload", handler);
return () => window.removeEventListener("beforeunload", handler);
}, [isDirty]);
return blocker;
}
export function EditProfile() {
const { formState: { isDirty } } = useFormContext();
const blocker = useUnsavedChangesWarning(isDirty);
return (
<>
{/* form fields */}
{blocker.state === "blocked" && (
<div role="alertdialog" aria-label="Unsaved changes">
<p>You have unsaved changes. Leave anyway?</p>
<button onClick={() => blocker.reset()}>Stay</button>
<button onClick={() => blocker.proceed()}>Leave</button>
</div>
)}
</>
);
}
Pitfalls
- Always-on beforeunload handlers (hurt bfcache and annoy users).
- Blocking navigation after a successful save (reset dirty state first).
- Custom text in beforeunload (browsers ignore it).
Variations
- Autosave drafts to localStorage or the server instead of blocking.
Further reading: React Router: useBlocker
Reference: https://reactrouter.com/api/hooks/useBlocker
More React recipes
- Add optimistic updates to a todo list
- Build an accessible modal dialog
- Build infinite scroll with TanStack Query
- Call a cookie-authenticated API with CSRF protection
- Debounce a search input in React
- Handle errors gracefully with error boundaries
- Implement dark mode that follows the system
- Poll a long-running job's status