Register a blocker only for unsaved changes and correctly connect proceed, stay, and tab-closing behavior.
Derive blocking from real dirty state
Returning true from shouldBlockFn blocks navigation. enableBeforeUnload covers browser-level refreshes and tab closes; enable it only while isDirty is true so clean pages do not prompt users.
Custom confirmation needs a resolver
With withResolver: true, render confirmation when status becomes blocked. proceed allows the navigation and reset keeps the current page. Clear dirty state after a successful save so later navigation is not blocked by stale state.
import { useBlocker } from '@tanstack/react-router'
function LeaveGuard({ isDirty }: { isDirty: boolean }) {
const blocker = useBlocker({
shouldBlockFn: () => isDirty,
enableBeforeUnload: isDirty,
withResolver: true,
})
if (blocker.status !== 'blocked') return null
return <div role="alertdialog" aria-modal="true" aria-labelledby="leave-title">
<h2 id="leave-title">Discard unsaved changes?</h2>
<button type="button" onClick={blocker.proceed}>Leave</button>
<button type="button" onClick={blocker.reset}>Stay</button>
</div>
}Turn this lesson into a verifiable skill
Client navigation and page unload are blocked only while the form is dirty and unblock immediately after save.
Implement leave, stay, and save-then-leave flows and write an interaction test for each.
Test Link, back, refresh, and save behavior; confirmation should appear only for unsaved state.
- Prompting for every navigation even when the form is unchanged.
- Blocking Router navigation while missing browser close or refresh.