When a parent layout level stays mounted across a navigation between two of its child routes, which state survives and which is lost?
answer
- reused means the same instance
- the split is at the first difference
- above keeps state, below is rebuilt
- timers and subscriptions survive with the level
- a persistent level can live all session
basics
~20 sEverything the still-mounted levels hold survives: their component state, their DOM state such as uncontrolled input values and open menus, and their timers, subscriptions and in-memory caches. The replaced subtree is torn down, so the outgoing route's own state is gone.
solid answer
~40 sStaying mounted means the same component instances keep living, not that the same markup is re-created. So every level above the first difference keeps its component state, its DOM state - an uncontrolled field's current value, an open disclosure, media that is playing - and anything it started: timers, subscriptions, event listeners, in-memory caches. The subtree from the first difference downward is destroyed, so an in-progress form or a local filter inside the replaced route is gone and a return visit starts from initial values. Both halves bite in production: drafts vanish from swapped children, while shared levels quietly accumulate state and subscriptions across a long session.
go deeper
Learn the two halves: the parts of the page that stayed on screen kept their state, and the part that was swapped out started over. That explains most 'why did my form empty' reports.
Explain that reuse means the same instance, so its effects keep running. Be able to say which of component state, DOM state and subscriptions survive, and why a return visit is a fresh instance.
Bring the production consequences: lost drafts on swap, shared controls that persist past their relevance, and subscriptions or caches that accumulate for a whole session on a level that never unmounts.
Position in the chain is a lifetime decision. Set expectations for what a long-lived shared level may hold, and make resets and teardown explicit rather than emergent from where a file happened to be placed.
## What 'stays mounted' really means When the router reuses a level it does not re-create it from scratch and hand it back its old markup. It leaves the **same instance running**. The distinction matters because an instance owns more than what you can see: the state it holds, the DOM nodes it created, and every side effect it started and has not cleaned up. The chain therefore splits at the first level that differs between the old and new match. Above that point, instances live on. At and below it, instances are torn down and new ones are created. ## What survives - **Component state** held by the reused levels - open panels, selected tabs, cached form values. - **DOM state** in the nodes they own: an uncontrolled input's current value, an open menu, the position of a media element that is playing, an element's current focus if focus sits in that subtree. - **Timers and intervals** started by those levels. - **Subscriptions** - sockets, event-source connections, store listeners - opened there. - **In-memory caches and refs**, including anything large the level pulled in and held. - **Providers and their values**, so consumers below can be swapped out and back without the provider re-initialising. ## What is lost - Everything the **replaced subtree** owned: its component state, its refs, its uncontrolled DOM values. - Its **effects are cleaned up** as it unmounts - its timers stop, its subscriptions close - assuming they were registered for cleanup. - A return visit to the same route builds a **new instance** with initial state, even though the URL is identical to the one the user just left. | Held by | On navigation between siblings | |---|---| | A reused level above the change | Survives - same instance, same state, same effects running | | The replaced level and below | Destroyed - state gone, effects cleaned up | | The URL itself | Survives by definition; it is the only state the router guarantees | ## Where this bites in production 1. **The vanished draft.** A user half-fills a form, clicks something in the shared sidebar, comes back, and the fields are empty. Nothing is broken; the subtree was destroyed. If losing that draft is unacceptable, the value has to live somewhere that outlives the subtree - the URL, storage, or a level above the one being swapped - or the app has to confirm before the navigation. 2. **The stale shared control.** A filter panel or a selection lives on a shared level, so it survives a move to a child where those filters mean nothing - and is still set when the user comes back to a route where they do. Persisting is correct behaviour for a mounted level; what is missing is an explicit reset when the child identity changes. 3. **The long session leak.** A shared level near the top may stay mounted for the whole session. Subscriptions it opens per visit, arrays it appends to, caches it never evicts and listeners it registers are never collected, because the thing that would have cleaned them up - unmounting - never happens. 4. **The resurrected effect.** Code written assuming 'this runs once per visit' runs once per *mount*. On a persistent level that can be once per session; on a replaced level it is once per visit. The same code in two positions in the chain has two lifetimes. ## Making the behaviour deliberate - **Derive from the URL** what should reset with the route. Anything encoded in the path or query is re-read on every navigation and cannot go stale in a mounted level. - **Reset explicitly** when the child identity changes, rather than hoping the level will re-initialise. A mounted level is told nothing by the router; it has to observe that the matched child changed. - **Guard destructive navigations** when a replaced subtree holds work the user would mourn. - **Budget what a persistent level holds.** Treat anything it accumulates as session-lifetime until proven otherwise, and cap or evict it. - **Choose the level for the lifetime you want.** Placing something one level higher or lower in the chain is the cheapest possible lever on how long it lives. Frameworks differ in the escape hatches they offer for forcing a reused level to start over, but the underlying rule - reused means the same instance, replaced means a new one - holds everywhere the chain model does.
- Why can code that 'runs once per visit' run once per session instead?Because it runs once per mount, and a level that keeps matching identically is never unmounted. On a replaced child that is once per visit; on a shared level near the top of the chain the same code may run once for the entire session. Position in the chain, not the code, decides the lifetime.
- How would you make a shared control reset when the route beneath it changes?Either move the value into the URL, so every navigation re-reads it and no reset is needed, or have the level observe which child is currently matched and reset its own state when that identity changes. The router will not reset a mounted level for you - it reuses it precisely so that it does not.
saying these in an interview costs you the question
- Assumes every navigation remounts the whole tree, so nothing can survive
- Thinks a draft in the replaced child is kept because navigation stayed client-side
- Believes only component state survives, not timers, subscriptions or DOM state
- Treats state held on a shared level as free, ignoring a whole session of it
- Expects a mounted level to reset itself when the route beneath it changes