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
- Accessibility Patterns in ReactPractical React a11y: labels, focus management, keyboard, live regions, and composition patterns that stay accessible.
- Avoid Prop Drilling with CompositionStop threading props through intermediates: children slots, inversion of control, and when context is the right escape hatch.
- Batching State UpdatesHow React 18+ batches setState in events, timeouts, and promises: when updates flush and why double setState still works.
- Children Prop PatternsUsing children and slot props for flexible APIs: wrappers, compound components, and when to prefer explicit props.
- Client Component BoundariesWhere to put use client: push interactivity to leaves, serializable props, and children as server slots.