Why do we need keys in React lists?
Keys give React a stable identity for each item across renders. Without them React falls back to index-based matching, which corrupts state when items are inserted, removed, or reordered. A row keyed by index would 'inherit' the input value of whatever item used to be in that position. Use a stable id from your data; avoid array index when the list mutates.
Keys are how React tracks list items across renders.
What goes wrong without keys
Without explicit keys (or with index keys), React matches old and new children by position. Fine for static lists, broken for anything that mutates.
{items.map((item, i) => (
<Row key={i} item={item} />
))}Delete item 0 and every remaining row 'becomes' a different item to React. Internal state (uncontrolled input value, focus, animation progress) sticks to the position, not the data.
Concrete example
function Row({ label }: { label: string }) {
return <input defaultValue={label} />;
}
const [items, setItems] = useState(['a', 'b', 'c']);
{items.map((item, i) => <Row key={i} label={item} />)}Type 'hello' into the first input. Click 'remove first'. What was the second item is in position 0 — but the input still shows 'hello' (the position's state was preserved, not the item's).
Fix:
{items.map(item => <Row key={item.id} label={item.text} />)}What makes a good key
- Unique within the parent's children list.
- Stable across renders for the same item.
- From the data, not generated in render.
{items.map(item => <Row key={item.id} {...item} />)} // good
{items.map(item => <Row key={Math.random()} {...item} />)} // bad — remounts every render
{items.map((item, i) => <Row key={i} {...item} />)} // bad if list mutatesWhen index keys are fine
- The list is static (never changes).
- The list is only ever appended to (no insertions/reorders/deletions).
- Items have no state, refs, or animations.
A list of country names in a dropdown — index is fine. A list of editable form rows — never use index.
Keys also reset state
A common pattern: change the key on a component to force a full remount.
<Form key={userId} user={user} />This is the React-recommended way to 'reset state on prop change' instead of doing it in an effect.
Where keys live
Keys are passed to React on the immediate child of a map, not on the rendered DOM. They never appear in the resulting markup.
{items.map(item => (
<Fragment key={item.id}>
<td>{item.a}</td>
<td>{item.b}</td>
</Fragment>
))}Performance
Keys also help React's diffing skip work. If two trees have the same keys in the same positions, React knows nothing moved. Without keys (or with bad keys), React may destroy and recreate DOM nodes unnecessarily.
Follow-up questions
- •Why does React warn when keys are missing?
- •When is it safe to use array index as a key?
- •How do you use a key to reset state on prop change?
Common mistakes
- •Using array index in a list that mutates — state ends up on the wrong row.
- •Generating keys with Math.random() — every render remounts every row.
- •Putting key on the wrong element (a child of the .map() return).
Performance considerations
- •Good keys let React skip work entirely when items haven't moved. Bad keys cause unnecessary remounts, lose focus, kill animations, and reset child state — all CPU + UX cost.
Edge cases
- •Keys must be unique among siblings but can repeat across the tree.
- •Fragment shorthand doesn't accept a key — use explicit Fragment.
- •Server-rendered lists must match keys on hydration or you'll get warnings.
Real-world examples
- •Any table with sortable columns: index keys break the moment you sort. Drag-and-drop lists (Trello, Linear) require id keys. Chat messages need stable keys so streaming updates don't redo work.