React
key: the strategy that avoids bugs
The key is an element's identity in a list: tie it to the data, never the index, and it also becomes the explicit way to reset a component's state.
Key points
- The key tells React which element is which across renders.
- The index as key pins state to the position, not the data.
- A key generated during render remounts the component every pass.
- Changing the key resets state: that is an explicit reset, not a side effect.
The key is an element's identity in a list: tie it to the data and not to the index, and it also becomes the explicit way to reset a component's state when the subject changes.
What React does with the key
Across two renders React matches children by key: the same key means the same element, so React reuses the node and its state. Without a key it uses the position, and state stays attached to the position instead of the data.
The case that loses data
function TodoList({ items, onToggle }) {
return (
{items.map((todo, index) => (
onToggle(todo.id)} />
{todo.title}
))}
);
}
Delete the first row and the checkbox stays ticked on the row that takes its place: the key did not change, so React reused the node. With key={todo.id} the node follows the data and the checkbox moves with it.
A stable key without an id
A key generated during render, with Math.random() or crypto.randomUUID(), changes on every pass: React unmounts and remounts everything, and the field loses focus and text. Assign the id when the data is created, not when it is drawn.
// assign the id when the row is created, not while rendering
const withIds = (rows) => rows.map((row) => ({ ...row, id: crypto.randomUUID() }));
Reset state by changing the key
Changing the key unmounts the component and builds it again: the form restarts from initial values and effects run again. It is the shortest way to reset state, and the cost is everything the user had typed.
The command
npm install --save-dev eslint eslint-plugin-react
npx eslint src
The eslint-plugin-react plugin ships under the MIT licence and reports a missing key during lint; an unstable key passes instead, and remains the dangerous case.
Live records in the catalogue
Three records show lists with identity: table, which uses the row id, leads-data-table with selectable rows, and the input category for the fields inside those rows.
The limitation
The key does not arrive as a prop, so the component cannot read it: it exists only for React to match children. And no linter tells a stable key from a random one, because both are syntactically valid.
Where to continue
The warning React prints in the console is explained in the key warning in lists; long lists are in virtualisation; what can stay derived is in derived state; the framework comparison is in frontend framework comparison.
Frequently asked questions
Why is the index risky as a key?
Because the index describes the position, not the data. When the list reorders or loses an element, React reuses the wrong node and state stays with the position.
Do I always need an id?
Yes, sooner or later. If the data arrives without one, assign it when you create the item: a combination of fields is a stopgap that breaks on duplicates.
Does changing the key reset state?
Yes. A different key unmounts the component and creates a new one, so state restarts from the initial values and effects run again.
Why does the field lose focus on every keystroke?
Almost always because the key changes on every render: a random id or an object rebuilt in the list. The key must stay the same for the same piece of data.
Can the component read its own key?
No. React consumes it for matching and does not pass it as a prop; if you need the value, pass it under another name.