Why array indices make bad keys.
An index isn't tied to the item — it's tied to the position. Insert, delete, reorder, or filter the list and the same index now points to different data, so React reuses the wrong DOM node and component state: wrong input values, misplaced focus, stale state, broken animations. Use a stable id.
An array index is a position, not an identity — and React keys are supposed to be identities.
The core problem
React uses the key to match elements between renders: "the element with key X last render is the same element as key X this render." When the key is the array index, that promise is false the moment the list changes:
Before: [A(key=0), B(key=1), C(key=2)]
Delete A:
After: [B(key=0), C(key=1)]B is now key=0. React looks at key=0: "last render this was A, this render it's still key 0 — same element, just different props." So React reuses A's DOM node and component state for B. Every item effectively shifted, but React thinks the last one was removed.
What that actually breaks
- Wrong input values — you delete the first todo; the text you typed in the second todo's input now appears in the first row. The uncontrolled input state stuck with the position (key 0), not the item.
- Misplaced / lost focus — focus is tied to a DOM node; reuse the node for a different item and focus jumps.
- Stale component state — a row's local state (expanded? checked? hovered?) belongs to the position, so it sticks to whatever item lands at that index.
- Broken animations / transitions — enter/exit animations fire on the wrong elements because React misidentifies what was added/removed.
- Subtle, hard-to-trace bugs — and worse, silent: nothing errors, the UI is just wrong.
Why it seems to work
Index keys are fine for a list that is static — never reordered, inserted into, deleted from, or filtered — and whose items have no local state or focus. That's why people get away with it on simple read-only lists and then get burned when the list becomes dynamic.
The fix
Use a key that's tied to the item's identity and stable across renders: a database id, a UUID generated when the item is created. Not the index, not Math.random() (that remounts everything every render).
The framing
"An index keys by position, not identity. When you insert, delete, reorder, or filter the list, the same index points to a different item — so React thinks the element is unchanged, just with new props, and reuses the DOM node and component state for the wrong item. The symptoms are wrong input values, misplaced focus, stale local state, broken animations — and they're silent. Index keys only happen to work for static, stateless lists. The fix is a stable id derived from the data."
Follow-up questions
- •Walk through exactly what happens when you delete the first item of an index-keyed list.
- •When is using the index as a key actually acceptable?
- •Why are these bugs especially dangerous?
- •Why is Math.random() as a key even worse?
Common mistakes
- •Using the index because 'the list looks static' — then it becomes dynamic.
- •Assuming index keys only cause a console warning, not real bugs.
- •Not realizing the bugs are silent — no error, just wrong UI.
- •Switching to Math.random() to 'fix' it — which remounts everything.
Performance considerations
- •Index keys also cause unnecessary re-renders/DOM updates of items whose data didn't change but whose index did — on top of the correctness bugs.
Edge cases
- •Reordering, inserting at the front, or filtering the list.
- •List items with uncontrolled inputs or local state.
- •List items with enter/exit animations.
- •A list that's static today but becomes editable later.
Real-world examples
- •A todo list where deleting an item leaves the wrong text in the inputs.
- •A sortable table losing row selection/expansion state after a sort.