skip to content

A detail panel keeps the previous record's unsaved edits when a new record is selected; how does changing its key fix that, and what does it cost?

level: seniorimportance: should knowfreq 58%

answer

  1. same position, same instance
  2. inputs refresh, state persists
  3. a different key is a different child
  4. discard and rebuild, not a patch
  5. narrow the keyed boundary

basics

~20 s

The panel sits in the same tree position, so it is paired with the same instance and only its inputs refresh. Keying it by the record id makes each record a different child, discarding the old instance and its subtree.

solid answer

~50 s

The panel occupies one position in the tree, so on every selection change the runtime pairs it with the same instance and hands it new inputs. Anything inputs do not recompute — a draft value, an uncontrolled field's text, which sections are open, scroll position — survives: that is the leak. Give the panel a key derived from the record id and identity changes with the record: the runtime treats it as a different child, discards the old instance and its nodes, and builds a fresh one with fresh state. The cost is that this is total. The whole subtree is rebuilt, so focus and scroll are lost, entry animations replay, and children re-run their setup work. Put the key on the smallest component that owns per-record state, keep anything that should persist above that boundary, and reset explicitly when only part of the state is per-record.

go deeper

for a junior

Remember the cause: a component in the same position keeps the same instance, so its own state survives a data change. Changing its key is the lever that says start over.

for a middle

Explain both halves — why the stale state survives (paired instance, inputs refreshed, state untouched) and what a key change does (discard the old instance and its nodes, build a fresh subtree). Name at least three things the remount also throws away.

for a senior

Show the judgment: choose remount-by-key when the whole subtree is per-record, put the key on the narrowest component that owns that state, lift out anything that must persist, and know that the remount replays the subtree's mount-time work.

for a principal

Frame it as where entity identity belongs in the component tree. A convention that per-entity surfaces are keyed by the entity turns a recurring class of stale-state bugs into a structural property, at the price of remount cost you then have to budget for.

## Why the stale draft is the default, not a bug in the runtime Identity in a component tree is decided by the same pairing step that matches list children: for a child at a given position, is this the same child as last time? A detail panel rendered in one place, with only its inputs changing, is the same child every time. So the runtime pairs it with its existing instance and updates it: inputs are refreshed and the output is recomputed from them. What is *not* recomputed is everything the instance owns independently of its inputs: - state it initialised once — a draft copy of the record, an edit-mode flag, which tab or section is open; - host-node state the runtime never wrote — an uncontrolled field's current text, focus and caret, a scroll offset; - derived values it computed at creation and has not recomputed since; - pending work it started for the previous record — an in-flight save whose response is about to arrive. That is the leak: the record changed, the panel did not. ## The key as a deliberate discard Because identity is decided by the pairing, an author can change identity on purpose. Give the panel a key derived from the selected record's id and a new selection produces a child whose key the runtime has never seen. Its response is not a patch: 1. the previous instance is treated as gone — its teardown runs and its host nodes are removed; 2. a new instance is built for the new key, initialising its state from scratch; 3. its children are built too, since the whole subtree below the keyed element is new. This is why the technique is reliable. You are not enumerating the fields that must reset; you are declaring that a different record means a different panel, and every piece of per-record state is covered by construction — including state added to a child six months from now. ## What it costs | Concern | Remount by changing the key | Explicit reset when the record changes | |---|---|---| | Coverage | total: all state below the key, present and future | only what you remembered to clear | | Host nodes | destroyed and recreated | kept | | Focus and caret | lost | kept | | Scroll position inside the subtree | lost | kept | | Entry animations | replay for the whole subtree | do not replay | | Children's setup work | runs again, including subscriptions and requests they own | runs only if you trigger it | | Cost profile | scales with the size of the subtree | proportional to what you reset | | Maintenance | one expression | rots as fields and children are added | The honest summary: remounting is correct-by-construction and blunt; an explicit reset is surgical and easy to forget. Neither is the right answer everywhere. ## Choosing, and narrowing the boundary The decisive question is *how much of the subtree is genuinely per-record?* - If everything below the panel belongs to the record, remount by key. It is the smallest expression of the intent and it cannot be partially applied. - If some state must survive the selection change — a scroll container, a collapsed sidebar, an expensive cached list, a filter the user set — it must not live below the keyed element. Lift it above the boundary, or hold it in a store outside the component tree. - If only one or two things are per-record and the rest should persist, reset explicitly: recompute the affected state when the identifying input changes, and keep the nodes. And keep the boundary narrow. Keying the whole page or route by record id works and is the usual overshoot: it discards the shell, the navigation, the scroll position and every cached child along with the form. Put the key on the smallest component that actually owns per-record state. There is a second reason to be careful about *how wide* the keyed element is: a remount replays whatever mount-time work the subtree does. If a child fetches on creation, the remount refetches; if several do, the selection change becomes a burst of requests. That is often acceptable and sometimes exactly what you want — but it should be a decision, not a surprise. ## The same lever under different reactivity models What differs between frameworks is how much a normal input change would have done on its own. In a runtime that re-runs the component function, a plain update already re-executes the body and the stale state is the state it deliberately preserves across runs. In one with fine-grained tracking, an input change updates only the cells that depend on it, and the untouched cells are the stale ones. In a compile-time runtime, the generated update path touches exactly the bindings the compiler linked to the changed input. In all three, changing the key means *this is a different child*, and in all three the answer is a discard-and-build rather than a smarter update — because identity, not the diff, is what you changed. ## The reviewable rule When a component's state is meaningful only for one entity, make that entity part of the component's identity. Then "reset when the record changes" is not a checklist maintained by hand; it is a consequence of what you declared.

  • How do you keep the scroll position while still resetting the form?
    Keep the scroll container above the keyed element, so it is never part of what gets discarded, and key only the component that owns the draft. If the scroll happens to live inside that component, the alternatives are lifting it out or resetting the draft explicitly and leaving the nodes in place.
  • When is an explicit reset the better choice?
    When only part of the subtree is per-record, when focus or an animation must survive the change, or when remounting would replay expensive setup work such as several children refetching. The price is that it is per-concern: each new field or child is a chance to forget one, so it needs a test that asserts the reset.
  • Can the same technique force a fresh start for a whole tab or route?
    Yes — keying a boundary by tab or route identity discards everything below it, which is sometimes exactly the intent. But the cost grows with the subtree, and it takes caches, scroll positions and user-set filters with it. That is the argument for keying the narrowest component that owns the per-entity state.

saying these in an interview costs you the question

  • Expects new inputs to clear a panel's internal draft state
  • Thinks changing a key patches the existing instance in place
  • Keys the whole page so every selection rebuilds everything
  • Assumes a remount preserves focus, scroll and animations
  • Clears one field by hand and calls the panel reset
  • Treats remount-by-key as a hack rather than a stated intent