How should a router stop a user leaving a form with unsaved changes, and what can it not stop?
answer
- two exits, two different powers
- dirty, not merely mounted
- held before the commit
- unload can be asked, not blocked
- unregister on save and unmount
basics
~20 sRegister a blocker while the form is dirty: the router consults it before committing any navigation it controls, holding the transition open until the user answers. A real document unload can only be asked about, never blocked.
solid answer
~50 sTwo exits need two mechanisms. Navigations the router owns — links, programmatic calls, back and forward — can be genuinely intercepted: while the form is dirty it registers a blocker, the router consults it before commit, and the pending transition waits on the user's answer, shown in the app's own dialog with the app's own wording. A real document unload — a reload, the tab closing, an address typed in the bar, a link leaving the origin — is not the router's navigation at all; the app can only signal that it wants to ask, and the browser shows a generic prompt it controls and may skip. So the two are not interchangeable: you need both, the blocker must be unregistered when the form saves or unmounts, and persisting a draft makes the prompt a courtesy rather than the only defence.
go deeper
Know that navigations inside the app can be intercepted and confirmed by the app itself, while closing or reloading the tab can only trigger the browser's own generic prompt.
Explain that the blocker is consulted before the transition commits, that the condition must be the form's dirty state, and that it has to be unregistered when the form saves or unmounts.
Demonstrate the traversal case: the address has already moved, abandoning means traversing back, the corrective move must not re-trigger the blocker, and a one-shot confirmed flag silently lets the next exit through.
The call you own is whether to defend with a dialog or design the loss away with drafts, plus a single policy for which system-initiated navigations — timeouts, broadcasts, redirects — are allowed through.
## Two exits, two mechanisms "Are you sure you want to leave?" is really two problems that share a sentence. | | Navigation the router owns | A real document unload | |---|---|---| | Examples | A link inside the app, a programmatic navigation, Back and Forward | Reload, closing the tab, typing an address, a link to another origin | | Can it be stopped? | Yes — the transition is held before it commits | No — the app can only request that the user be asked | | Who draws the dialog | The app | The browser | | Can you word it? | Yes | No; the text is generic | | Does the app learn the outcome? | Yes, it resolves the transition itself | Not reliably | | Is it guaranteed to appear? | Yes | No; browsers may skip it, for example when the user has not interacted with the page | The practical consequence is that neither one covers the other, so a form with expensive unsaved work registers both, and knows the second is weaker. ## How a blocker works 1. **Register while there is something to lose.** The condition is *dirty*, not *mounted* — a pristine form must never prompt, and this is the single most common complaint about these dialogs. 2. **The router consults it before committing.** The check happens after the navigation has been started and resolved, and before the current screen is torn down; the transition stays pending while the question is open. 3. **The user answers in the app's own dialog** — its wording, its buttons, and, if the design allows, a third option that saves and then continues. 4. **The blocker resolves the transition.** Confirm means proceed with the navigation that was held; abandon means the transition never commits and the current screen simply stays. 5. **Unregister on save, on discard, and on unmount.** A blocker left registered strands the user on a screen that argues with every exit, and it will outlive the reason it existed. ## The back-navigation wrinkle A traversal is not like a link click: by the time the app is consulted, the session has already moved to the destination entry while the old screen is still painted. That produces a class of bugs worth naming in an interview. - **Abandoning means traversing back**, not just declining to commit — otherwise the address and the screen disagree. - That corrective traversal must not be treated as **fresh user intent**, or the blocker fires again immediately and the dialog loops. - After the user confirms, pressing Back again must re-enter the blocker only if the form is *still* dirty. Apps that set a one-shot "the user already agreed" flag and never clear it let the second exit through silently. - A blocker that is asked about a navigation which **stays inside the same form** — a tab within it, or a refinement of the current address — should say yes. Scope the condition to navigations that actually leave. ## Why the platform prompt is the weaker half The unload confirmation is deliberately constrained, because it is the one dialog a hostile page could use to trap a user. The app cannot word it, cannot style it, cannot know what the user chose, and cannot rely on it appearing at all; on mobile in particular, a tab being backgrounded and later discarded may never produce it. Treat it as a courtesy for the reload-by-accident case, never as the mechanism that protects the user's work, and register it only while the form is dirty — always-on unload handlers have costs beyond the dialog itself. Whether unload time is a reliable moment to *persist* anything is a platform-lifecycle question with its own answer, and the honest engineering conclusion is the same either way. ## Design the need away The strongest version of this feature prompts rarely. - **Persist a draft as the user types**, locally or to the server, so leaving costs nothing and the prompt becomes advisory. - **Make the dirty check honest.** Compare against the loaded values rather than tracking that a field was focused, so reverting a change clears the blocker. - **Offer save-and-continue** in the dialog; users almost always wanted that rather than a binary. - **Consider what else can leave.** A session timeout, a broadcast from another tab, or a redirect the app itself performs all pass through the router and will be blocked — decide deliberately whether each should be. That framing is what an interviewer is listening for: the mechanism, the asymmetry between the two exits, and the recognition that a dialog is a poor substitute for not losing the work.
- Why is the platform's unload confirmation not enough on its own?Because it is generic, unstyleable, gives no reliable outcome signal, may be skipped when the user has not interacted, and never fires for a navigation the router handles in-app. It covers only reload and tab-close, and weakly — which is why an in-app blocker exists for everything the router controls.
- A user confirms leaving, then presses Back again and is not prompted. What went wrong?The app almost certainly set a one-shot flag meaning "already confirmed" and never cleared it after the transition resolved. The condition must be the form's current dirty state, re-evaluated per navigation, so a still-dirty form prompts again and a saved one never does.
- How should the blocker treat a navigation that stays within the same form?It should allow it. Switching a tab inside the form, or refining the current address, does not lose anything, so prompting there teaches users to dismiss the dialog reflexively. Scope the condition to navigations that leave the form's own subtree of addresses.
- What makes the blocker mostly unnecessary?Persisting a draft as the user types. If leaving loses nothing, the dialog becomes an optional courtesy instead of the only thing standing between the user and lost work — and it removes the failure mode where the weaker unload path is the one the user actually took.
saying these in an interview costs you the question
- The unload prompt can be customised with your own message
- The unload prompt also fires on in-app navigations
- One mechanism covers both leaving the form and closing the tab
- Register the blocker whenever the form is mounted
- Blocking back just means pushing the entry again
- The prompt is enough; no need to persist a draft