Lists and Keys
BeginnerA key tells React which item is which between renders. Using the array index is the single most common source of "the wrong row updated" bugs.
Overview
Rendering a list is a map call, which everybody gets right immediately. The key is the part that goes wrong. React uses keys to match elements from the previous render to the next one, deciding what to keep, move, or destroy. If the key is the array index, then removing the first item shifts every key by one, so React believes each row became a different row — and any state inside those rows, including what the user typed and where the cursor was, stays with the position rather than the data.
Rendering a List
map plus a stable key from the data itself.
<ul>
{problems.map(problem => (
<ProblemRow key={problem.id} problem={problem} />
))}
</ul>
// The key goes on the outermost element of the map, not inside it
{rows.map(r => <Fragment key={r.id}><dt>{r.k}</dt><dd>{r.v}</dd></Fragment>)}
// Keys must be unique among SIBLINGS, not globally.
// Two different lists can both use key={1}.
// Filtering and sorting are just JavaScript
{problems
.filter(p => p.difficulty === level)
.sort((a, b) => a.order - b.order)
.map(p => <ProblemRow key={p.id} problem={p} />)}
// Empty state, always
{problems.length === 0
? <EmptyState />
: problems.map(...)}Why the Index Breaks
A concrete failure, because the abstract rule never convinces anyone.
// A list of inputs, keyed by index
{todos.map((todo, i) => <TodoInput key={i} todo={todo} />)}
// State: ['Buy milk', 'Call Ravi', 'Ship release']
// keys: 0 1 2
// Delete 'Buy milk'. Now:
// State: ['Call Ravi', 'Ship release']
// keys: 0 1
// React sees key 0 still exists and keeps the DOM node and its
// internal state. The text the user had typed into row 0 — and the
// focus, and the checkbox state — are now attached to 'Call Ravi'.
// With key={todo.id}, React sees the id for 'Buy milk' is gone,
// removes exactly that row, and every other row keeps its own state.
// The index is safe ONLY when the list never reorders, never has
// items inserted or removed, and the rows hold no state.Finding a Stable Key
What to use when the data has no id.
key={problem.id} // best — a real id
key={`${row.userId}-${row.topic}`} // a composite natural key
key={item.slug}
// Avoid: Math.random() and crypto.randomUUID() inline —
// a new key every render means React destroys and rebuilds the
// entire list each time. It "fixes" the warning and ruins performance.
key={Math.random()} // never
// If the data genuinely has no id, generate one when the item is
// created and store it with the item:
setRows(rows => [...rows, { id: crypto.randomUUID(), value: '' }])
// A changing key is also a deliberate tool: change the key on a
// component to reset all of its internal state.
<ProblemEditor key={problem.slug} problem={problem} />
// Switching problems now gives a clean editor rather than the
// previous problem's draft.Key Points to Remember
- 1Keys let React match elements between renders; they must be unique among siblings, not globally
- 2An index key attaches state to a position, so deleting or reordering moves state onto the wrong item
- 3Index keys are safe only for a static list that never reorders and whose rows hold no state
- 4A random key regenerated each render destroys and rebuilds the whole list
- 5Changing a component's key deliberately resets all of its internal state
Interview Questions
Sign in to ask AriaWhat does React use the key prop for?
Give a concrete bug caused by using the array index as a key.
How can you force a component to reset all of its state?
Ask Aria about Lists and Keys
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.