How to Fix React's "Unique Key Prop" Warning
"Each child in a list should have a unique 'key' prop" is not just console noise — it is React telling you it cannot reliably track your list items. Here is why keys matter, and how to give them the right ones.
Warning: Each child in a list should have a unique "key" prop. shows up the first time you render an array in React, and the temptation is to make it go away as fast as possible — slap key={index} on the element and move on. That silences the console, but it misunderstands what React is actually asking for. The warning is a correctness signal, not a style nag: React is telling you it has no reliable way to identify the items in your list from one render to the next. Get the key wrong and you don't get an error — you get a subtle, hard-to-reproduce UI bug that only appears once the list changes shape.
What the warning actually means
React shows Each child in a list should have a unique "key" prop when you render an array of elements without giving each one a key. React uses keys to match elements across renders so it knows which items were added, removed, or moved. Without stable keys it falls back to matching by array position, which reuses the wrong nodes and state when the list changes.
Every time your component re-renders, React produces a fresh tree of elements and compares it against the previous one to work out the minimum set of DOM changes. For a list, the question it has to answer is: is this the same item as before, or a different one? The key is your answer to that question. It's a hint that says "this element corresponds to that piece of data," so React can carry the matching DOM node and its state forward. Remove the key and React can only guess by position — which is exactly the guess that goes wrong the moment items move.
Why keys matter more than the warning suggests
Here's the part most "just add a key" answers skip. A key doesn't only prevent a console warning — it establishes the identity of each list item across renders, and identity is what React uses to decide which component instance keeps its state. Two elements with the same key at the same position are treated as "the same thing that changed." Two elements with different keys are treated as "one thing was removed and a different thing was added" — which throws away the old instance's state and mounts a fresh one.
That means keys quietly control three things you care about: which DOM nodes get reused (performance), which component state stays attached to which item (correctness), and which enter/exit animations play (polish). When the keys are stable and unique, all three follow the data. When they're missing or shift around, React attaches the wrong state to the wrong item, and the bug shows up in the UI long after the warning scrolled out of the console.
The four ways keys go wrong (and how to fix each)
1. No key at all on a .map(). This is the literal cause of the warning: you returned an array of elements and none of them carry a key. The fix is to add one, sourced from a stable field on the data itself.
// ⚠️ Warning: each child needs a unique "key" prop
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo) => (
<li>{todo.text}</li>
))}
</ul>
);
}
// ✅ Give each item a stable key from the data
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}2. Using the array index as the key. This is the seductive one: key={index} makes the warning disappear and looks perfectly valid, because the indexes really are unique within one render. The problem is that an index describes a position, not an item. When the list reorders, inserts at the front, or deletes from the middle, the item that was at index 2 is now at index 1 — but React still thinks index 2 is "the same item," so it reuses the old DOM node and state for whatever now sits there.
// 😬 Works, but the index is a position — not an identity
{items.map((item, index) => (
<Row key={index} item={item} />
))}
// ✅ Use an id that travels with the item when it moves
{items.map((item) => (
<Row key={item.id} item={item} />
))}3. Non-unique keys (duplicate ids). A key only helps if it's actually unique among its siblings. If two items share an id — a common result of concatenating two data sources, or a backend that recycles ids — React can't tell them apart, warns again, and reuses nodes unpredictably. Make sure the field you key on is genuinely unique, or derive a composite key.
// ⚠️ Two sources can produce colliding ids
const merged = [...featured, ...regular];
merged.map((post) => <Card key={post.id} post={post} />);
// ✅ Namespace the key so siblings stay unique
merged.map((post) => (
<Card key={`${post.source}-${post.id}`} post={post} />
));4. Key on the wrong element. The key has to live on the outermost element the map callback returns — the one React is placing directly into the array — not on a child nested inside it. If you wrap the returned siblings in a Fragment, the shorthand <>…</> can't take a key, so you must use the explicit Fragment import.
import { Fragment } from 'react';
// ❌ Key is on an inner child, not the returned root
{people.map((p) => (
<>
<dt key={p.id}>{p.name}</dt>
<dd>{p.bio}</dd>
</>
))}
// ✅ Key belongs on the Fragment React actually maps over
{people.map((p) => (
<Fragment key={p.id}>
<dt>{p.name}</dt>
<dd>{p.bio}</dd>
</Fragment>
))}The gotcha: index keys break stateful lists
The clearest way to feel why this matters is a list of inputs. Render a set of rows, each with its own uncontrolled text field, and key them by index. Type something into the second row, then reorder the list so that row moves to the top. Because the keys are 0, 1, 2… and they're tied to position, React sees "index 0 is still index 0" and keeps the first row's DOM node — including the text you typed and the cursor focus — even though the underlying item at that position is now different. Your text stays put while the data slides out from under it.
// 🐛 Reordering shuffles the data but not the keyed DOM state
function Editors({ rows, onReorder }) {
return rows.map((row, index) => (
<input key={index} defaultValue={row.label} />
));
// Move row 2 to the top -> the typed text / focus stays on
// the OLD position, now showing the WRONG item's field.
}
// ✅ Key by identity: state follows the item as it moves
function Editors({ rows }) {
return rows.map((row) => (
<input key={row.id} defaultValue={row.label} />
));
}With key={row.id}, React recognizes that the item moved and carries its DOM node — text, focus, animation state — along with it to the new position. The rule that falls out of this: if a list can change order and its rows hold any state, the key must be a stable id from the data, never the index. This same identity mechanism is why a mis-keyed list can also feed a re-render loop; if you land on "Maximum update depth exceeded" while wrangling a dynamic list, the identity of your items is a good place to look.
The correct pattern
The durable fix is boring on purpose: give every piece of data a stable, unique id at its source — the database row id, a UUID assigned when the item is created, a slug — and key on that. Don't generate the id inside render (key={Math.random()} or key={crypto.randomUUID()} at the call site defeats the whole point, because a brand-new key every render tells React every item is new, so it remounts the entire list each time). If your data genuinely has no id, assign one once when the item enters your state, not on each render.
Keys are also scoped to a single array: they only need to be unique among siblings, so it's fine for two different lists on the page to both use key="1". And the key itself is never passed to your component as a prop — if you need the id inside the child, pass it a second time explicitly (<Row key={id} id={id} />).
When the console warning isn't enough
The frustrating thing about key bugs is the gap between the warning and the symptom. The console tells you "add a key" — but the real failure is visual and stateful: a checkbox that stays checked on the wrong row after a sort, an input whose text follows the old position, an animation that fires on the wrong element. By the time a user reports "the wrong row got selected," the warning is long gone and the bug only reproduces after a specific sequence of reorders you have to guess at.
Key correctness is one of those React fundamentals that pays off across your whole app: it's the same identity model behind why a component resets its state when you change its key on purpose, and behind clean list animations. If you want a broader system for turning UI bugs like this into fast, reliable reproductions, our guide on catching, reproducing, and fixing React errors covers the workflow end to end.
Install the free BugMojo extension and capture the exact session — replay, console, and network — when a list renders the wrong row state, so you watch the reorder happen instead of guessing at the reproduction.
Install the free extensionFrequently asked questions
Frequently asked questions
Sources
- Rendering Lists — React docs: how to render arrays and why each item needs a key — React (2026)
- Keeping list items in order with key — React docs on stable keys and why index-as-key is discouraged — React (2026)
- Preserving and Resetting State — React docs: keys control component identity, so they decide which state is kept or reset — React (2026)
- Array.prototype.map() — MDN reference for the array method that produces the element list React renders — MDN Web Docs (2026)
Get bug-tracking insights, weekly.
Engineering deep-dives, QA playbooks, and honest tool comparisons. No spam — unsubscribe in one click.

