React keys — why they matter and the most common mistake
When you render a list, React uses the key prop to figure out which items changed, were added, or removed. Without stable keys, React falls back to index-based diffing and breaks state preservation, focus, and animations.
Bad — using array index as key
const todos = [{ id: 1, text: 'Buy milk' }, { id: 2, text: 'Walk dog' }];
return todos.map((todo, idx) => (
<li key={idx}>{todo.text}</li> // ❌ index as key
));
If you delete the first todo, the second todo's key changes from 1 to 0. React thinks "key 0 changed text from 'Buy milk' to 'Walk dog'" and reuses the same DOM node — but state inside that <li> (a checkbox, an open menu, input focus) sticks to the wrong row.
Good — stable, unique ID
return todos.map((todo) => (
<li key={todo.id}>{todo.text}</li> // ✅ stable identity
));
The bug index-keys cause — visual
function TodoRow({ todo }) {
const [editing, setEditing] = useState(false);
return editing ? <input defaultValue={todo.text} /> : <p>{todo.text}</p>;
}
// Parent
todos.map((t, idx) => <TodoRow key={idx} todo={t} />);
Open the editor on row 2. Delete row 1. Now row 2 shows row 3's text — but the editor is still open with row 2's old text in the input. State stayed with the index, not the data.
When is index actually OK?
- The list is static (never reordered, never inserted, never removed)
- The list items are stateless (no inputs, no focus, no inner state)
Even then, prefer a real ID — it's free safety.
Keys must be unique among siblings, not globally
<>
<ul>{users.map((u) => <li key={u.id}>{u.name}</li>)}</ul>
<ul>{posts.map((p) => <li key={p.id}>{p.title}</li>)}</ul>
</>
A user with id 5 and a post with id 5 is fine — they're in different lists.
Keys are NOT a prop
function Row({ key, item }) { // ❌ key is not accessible inside the child
console.log(key); // undefined
}
key is consumed by React itself. If the child needs the id, pass it as a separate prop.
Common interview question — using Math.random() as key
items.map((x) => <li key={Math.random()}>{x}</li>); // ❌
A new random key every render → React thinks every item is brand new → unmount + remount every render → catastrophic perf + lost state. Don't.
When no good id exists
If the data has no ID and you control it, generate one:
const items = useMemo(
() => raw.map((text) => ({ id: crypto.randomUUID(), text })),
[raw],
);
Or use a deterministic composite key:
<li key={`${item.type}:${item.name}`}>...</li>
What React actually does with keys
Internally, React uses keys to build a map of children. On re-render, it matches new children to old by key, deciding to keep, update, move, or unmount each one. Stable keys = stable matching = stable DOM = preserved state.