Keys and Remounting State
Keys control identity during reconciliation: stable ids, index key bugs, and intentional remounts when state must reset.
- react
- keys-and
Keys are not “for React performance tips on Twitter.” They are how React matches list children across renders so the right fiber keeps its state, DOM node, and effects. Wrong keys look like mysterious input glitches and teleporting local state.
Docs: Rendering Lists, Preserving and Resetting State.
The rule
Among siblings, each child needs a key that is:
- Stable — the same item keeps the same key across renders.
- Unique among siblings — not globally unique forever, but unique in that list.
- Derived from data identity — usually a database id, not the array index.
// Good
{todos.map((todo) => (
<TodoRow key={todo.id} todo={todo} />
))}
// Bad when the list inserts, deletes, or reorders
{todos.map((todo, index) => (
<TodoRow key={index} todo={todo} />
))}
Index keys fail when you unshift, sort, or filter. React reuses the fiber at position 0 for whatever item is now first — so the input state of the old first row sticks to a different todo.
Keys outside lists
Any place React needs to distinguish siblings benefits from keys — including fragments of multiple nodes. You can also force remount by changing a key on purpose:
function UserEditor({ userId }) {
return <ProfileForm key={userId} userId={userId} />;
}
When userId changes, ProfileForm mounts fresh: new state, new effects. That is the recommended alternative to syncing props into state with effects. See resetting state with key and preserving state on reorder.
What key is not
- Not a prop your component reads as
props.key(it is not passed through). - Not a CSS selector.
- Not a substitute for a missing stable id — if the server sends no id, generate one when the item is created and keep it in your model.
- Not “Math.random() each render” — that remounts every time and destroys performance and focus.
// Catastrophic
<Row key={Math.random()} />
Reconciliation snapshot
On update, React walks old children vs new:
| Situation | Result |
|---|---|
| Same key + same type | Update in place; state kept |
| Same key + different type | Remount (type change wins) |
| Key missing / moved | Rematch by key; state follows the key |
| Index key + reorder | State sticks to index, not item |
function Board({ cards }) {
return cards.map((card) => (
<Card
key={card.id}
title={card.title}
// local useState inside Card follows card.id across drags
/>
));
}
Interview out-loud
“Keys give list children a stable identity so reconciliation reuses the correct fiber and state. I use business ids, never random keys, and I avoid index keys when the list can reorder. Changing a key intentionally remounts a subtree to reset state.”
Related on this site
Further reading
Production checklist
Before you ship a change in this area, walk the list out loud:
- What is the source of truth for the data on screen?
- What happens on remount, route change, and Strict Mode double-invoke?
- Which updates are urgent (input) versus deferrable (filter large lists)?
- Did you profile before adding memo, context splits, or virtualization?
- Is there an accessibility path: keyboard, focus, names, and errors?
If you cannot answer those, the API knowledge will not save the interview or the incident.
Related guides
- useCopyToClipboard HookCopy text with navigator.clipboard, fallback execCommand, transient copied state, and permissions that fail in insecure contexts.
- useDebouncedValue HookDebounce a changing value in React without firing effects on every keystroke. Cleanup, leading vs trailing, and the stale timer that updates unmounted state.
- useEventListener HookAttach a window, document, or element listener with a stable handler ref so you do not rebind on every render. Capture option and target changes.
- useFetch With AbortControllerFetch in an effect with a signal, ignore aborted errors, and prevent the slow response from winning a race after the url changes.
- useIdle HookDetect idle users: reset a timer on input events, fire idle after timeout, and decide whether mount starts the timer immediately.