Lists and Keys
Rendering arrays of data with map, and why the key prop matters more than it looks like it should.
2 min read
Rendering a list in React is just JavaScript's Array.prototype.map, transforming an array of data into an array of JSX elements:
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}If you try this without the key prop, React logs a console warning: Warning: Each child in a list should have a unique "key" prop. It'll usually still render — but skipping key isn't just a linting formality, it can cause real, hard-to-spot bugs.
Why keys exist
When a list re-renders, React needs to match up each element in the new array with the corresponding element from the previous render, to know which DOM nodes to update, add, or remove, and which component instances (and their state) should carry over rather than being recreated. key is the identity React uses to make that match — it's not passed to your component as a prop, it's metadata React reads for itself.
Without a stable key, React falls back to matching elements by their position in the array, which works fine as long as the list order and contents never change — and breaks as soon as items are added, removed, or reordered.
Why index as a key can go wrong
Using the array index as a key looks like it satisfies the "give it a key" requirement, but it recreates the position-based matching problem:
// Works fine for a static list, unreliable for anything else
{todos.map((todo, index) => (
<li key={index}>{todo.text}</li>
))}Say a list has items with keys 0, 1, 2 and you delete the first one. The remaining items shift to 0, 1 — so React sees "the same keys, with different content," and matches the old item at index 0 to the new item now sitting at index 0, rather than recognizing that this used to be the second item. If any list item holds its own state (an input's typed value, a checked checkbox), that state ends up attached to the wrong row after the reorder — a real, visible bug, not just a performance concern.
Use a stable, unique identifier
The fix is to key by something that uniquely and durably identifies the data, independent of its position — a database ID, a UUID, or any field guaranteed unique within that list:
{todos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}The array index is an acceptable fallback only when a list is guaranteed static — never reordered, filtered, or has items inserted or removed — which in practice is a narrower case than it first appears.
With static and dynamic rendering covered, the next lesson moves to handling user input through forms — starting with the controlled-input pattern React expects.