skip to content

A React team fixed a stale detail panel by putting key={selectedId} on their top-level page component, and now switching records also collapses the sidebar, jumps the scroll to the top and re-initialises an expensive chart. How do you decide which subtree the key actually belongs on?

level: seniorimportance: should knowfreq 44%

answer

  1. a key sets the blast radius
  2. start from the state that must die
  3. split the component so the key can move down
  4. expensive mounts stay outside the boundary
  5. too low leaves the original bug alive

basics

~20 s

Put the key on the smallest component that owns exactly the state that should die with the identity change. If that state is mixed with state that must survive, extract the identity-scoped part into its own component and key that one.

solid answer

~40 s

A key destroys everything below it, so its placement is a scope decision, not a syntax detail. I start from the state that must be discarded, find the lowest component that owns all of it, and put the key there. If state that should survive — sidebar flags, scroll containers, a mounted chart or editor — sits inside that component, I split it: hoist the surviving state up, pull the identity-scoped part into a child, and key the child. Anything expensive to mount stays outside the keyed boundary, because a remount pays the full mount cost again, tears down DOM nodes, discards refs and re-runs effects. The test I apply is: everything under this key is meaningless once the identity changes. If I cannot say that honestly, the key is too high.

go deeper

for a junior

Know that a key resets the component it is on plus everything rendered inside it, so putting it too high wipes out unrelated parts of the screen.

for a middle

Explain the procedure: list the state that must clear, find the lowest component owning all of it, and check nothing valuable lives underneath before placing the key there.

for a senior

Demonstrate the restructuring instinct — splitting a component so the identity boundary is explicit, keeping expensive or imperative subtrees outside the key, and accounting for focus, scroll and mount cost.

for a principal

Frame the keyed boundary as an architectural line in the tree. Insist that components make identity scope visible, so a reset is a structural property a reviewer can see rather than behaviour buried in effects.

## The key is a scope declaration When a key changes, React ends the lifetime of that component *and its entire subtree*. Every `useState` below it, every ref, every DOM node, every running effect subscription goes away and is rebuilt. So choosing where the key goes is choosing the blast radius of the reset. Putting it on the page component is the easy fix and the reason the sidebar, scroll and chart all died. ## Work upward from the state you want gone A reliable procedure: 1. **Enumerate** the state that is only meaningful for the current identity — the draft fields, validation errors, the active tab within the record, unsaved toggles. 2. **Find the lowest common owner** of that list. That component is the natural key site. 3. **Check what else is inside it.** Walk the subtree for state that should survive: filters the user set, a collapsed sidebar, scroll offsets, focus, uncontrolled inputs, an imperatively created widget behind a ref. 4. **If something survives-worthy is inside, restructure** rather than compromise: lift that state to a parent above the key, or extract the identity-scoped part into its own component so the key can move down onto it. Step 4 is the part interviewers listen for. Reset-by-key frequently forces a component split, and that split is a feature — it makes the identity boundary explicit in the component tree instead of implicit in an effect. ```jsx function RecordPage({ selectedId }) { const [sidebarOpen, setSidebarOpen] = useState(true); // must survive return ( <Layout> <Sidebar open={sidebarOpen} onToggle={setSidebarOpen} /> <ExpensiveChart datasetId={selectedId} /> {/* outside the key */} <RecordForm key={selectedId} recordId={selectedId} /> {/* dies with identity */} </Layout> ); } ``` ## Cost lives at the boundary too Remounting is not free. Below the key you pay for: recreating DOM nodes, re-running every mount effect, re-establishing subscriptions, re-measuring layout, and any imperative setup a third-party widget does on mount. A canvas chart, a code editor, a media element or a virtualized list can cost tens of milliseconds each time. Those belong *outside* the keyed component, receiving the new id as a prop and updating imperatively, while the cheap state-holding form is what gets remounted. User-visible continuity is part of the cost: focus is lost when the focused node is inside the keyed subtree, scroll position of any scroll container inside it resets, CSS transitions and animations restart, and an uncontrolled input's DOM value goes back to its `defaultValue`. ## Two failure directions **Key too high** is the case in the question: the reset works but takes hostages. Symptoms are unrelated UI state resetting, visible flicker on switch, and a performance regression on navigation. **Key too low** is quieter and worse. If the key sits on a child while stale state lives in a sibling or in the parent, the bug you were chasing survives the fix and reappears in a different field. That is why step 1 is enumerating the state rather than guessing at a component. ## When no placement works Sometimes the surviving state and the resetting state are genuinely entangled — a wizard where the step index should persist but each step's answers should clear per record, for instance. Splitting is still usually possible: hoist the step index to a parent and key the step body. If it truly is not, stop using a remount and reset explicitly, either by adjusting state during render against a stored previous id or through a reset action on a reducer, which lets you name precisely which slices clear. ## The sentence to be able to say "Everything under this key is meaningless once the identity changes." If a reviewer can point at one thing inside the boundary that contradicts that sentence, the key is in the wrong place.

  • How would you keep an expensive chart alive while still resetting the form beside it?
    Render them as siblings and key only the form. The chart stays mounted and receives the new id as a prop, updating its data internally; the form is remounted and clears. If they are currently one component, that separation is the refactor — it turns an implicit identity boundary into an explicit one in the tree.
  • After a keyed remount, focus jumps to the document body. How do you handle that?
    Treat focus restoration as an explicit responsibility of the new instance, since remounting destroyed the focused node. Give the field a callback ref or a mount effect that focuses it deliberately, and only do so when the switch was user-initiated navigation, not on every mount, so you do not steal focus on first load.
  • Does a keyed remount unmount the old subtree before mounting the new one, and does that matter?
    React mounts the new subtree and unmounts the old one within the same commit, so cleanup functions for the old instance run as part of that commit. It matters when cleanup releases a shared resource the new instance immediately re-acquires — a socket, a lock, a media stream — because you may see a teardown/setup cycle you did not intend on every switch.

saying these in an interview costs you the question

  • Puts the key on the outermost component because it definitely works
  • Assumes remounting is free because React is fast
  • Forgets that refs and uncontrolled DOM values die with the subtree
  • Believes only the keyed component resets, not its children
  • Adds a key without checking what other state lives inside

context