Lists & Keys
Render arrays with .map and stable unique keys; avoid index keys, which cause state and DOM bugs on reorder.
Rendering arrays with .map#
Return an array of elements from .map. Each element needs a key.
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
Why keys must be stable & unique 🎯#
Keys are React's identity tags for list items during reconciliation. When a list changes, React uses keys to match new elements to previous ones so it can:
- Reuse the existing DOM node and component state for matched keys.
- Move nodes instead of recreating them.
- Add/remove only what actually changed.
Requirements:
| Requirement | Why |
|---|---|
| Unique among siblings | Duplicate keys → React can't distinguish items; warns and misbehaves |
| Stable across renders | A key that changes each render forces remount, losing state and DOM |
| Derived from data identity | Use a DB id / uuid, not the array index or Math.random() |
🟢 key={Math.random()} is an anti-pattern: it changes every render, so React remounts every row — destroying focus, animations, and local state, and killing performance.
The index-as-key pitfall ⚠️#
Using the array index as the key works only if the list is static — never reordered, filtered, inserted into, or deleted from. Otherwise indices stay 0,1,2… while the underlying items shift, so React mis-associates state with the wrong item.
// ❌ index key + reorderable/insertable list → bugs
{items.map((item, i) => <Row key={i} item={item} />)}
// ✅ stable identity
{items.map(item => <Row key={item.id} item={item} />)}
Classic symptom: an uncontrolled <input> inside each row keeps the text of the old position after you insert/delete/reorder, because React reused the DOM node bound to that index.
flowchart TD
subgraph Before
K0["key=0 (Apple)"]
K1["key=1 (Banana)"]
end
subgraph After["Insert 'Mango' at top (index keys)"]
N0["key=0 → now Mango, but React reuses Apple's DOM/state"]
N1["key=1 → now Apple, reuses Banana's node"]
end
K0 -->|"index reused, wrong item"| N0
K1 -->|"index reused, wrong item"| N1
Keys are for reconciliation, not props ⚠️🎯#
key is a reserved React hint consumed by the reconciler — it is not passed to the component. Reading props.key inside the child yields undefined. If the child needs the value, pass it under a different prop name.
// ❌ id is not readable as props.key inside Row
<Row key={item.id} />
// ✅ pass it separately if needed
<Row key={item.id} id={item.id} />
Bonus: changing a component's key is a deliberate way to force a remount (reset its state) — e.g. <Form key={userId} /> resets the form when switching users.
Interview Q&A#
Q1. Why does React require keys on list items? Keys give each item a stable identity so React can match items across renders during reconciliation, reusing/moving DOM nodes and state instead of destroying and recreating them.
Q2. What's wrong with using the array index as a key? Indices don't track item identity. On reorder, insert, or delete, React associates state/DOM with the wrong item, causing subtle bugs. It's only safe for static, never-changing lists.
Q3. Can a child component read its own key via props?
No. key is reserved for the reconciler and is not passed as a prop. Pass the value under a different prop name if the child needs it.
Q4. Why is key={Math.random()} bad?
It generates a new key every render, so React can't match items and remounts every row — losing state and focus and hurting performance.
Q5. How can changing a key reset a component?
Giving a component a new key makes React treat it as a different element, unmounting the old instance and mounting a fresh one, which resets its state.