Each child in a list should have a unique "key" prop

Filed under Next.js & React errors

Most likely causeAbout 10 min · 6 steps

A list rendered with .map() has items with no key, duplicated keys, or index keys — so React cannot tell items apart when the list changes.

Find the .map() the warning names
Set key to each item's real unique ID
Stop using the array index as the key

Fastest route

Give your AI this prompt

Copy it, replace the bracketed part with your error, and send it.

Preview the prompt
My React console shows this warning: "Each child in a list should have a unique 'key' prop", and it points at my [component name] component.

Here is the code that renders the list: [paste the component or the .map() block]. Here is a sample of the data being rendered: [paste 2-3 items].

Please: 1) show me exactly which element the key belongs on, 2) set the key to the field in my data that is unique and stable for each item, and tell me which field you chose and why, 3) if I am currently using the array index as the key, replace it and explain what could have gone wrong with it.

Constraints: do not use Math.random(), Date.now(), or the array index as the key, do not change anything else about how the list renders, and if the data has no unique field, tell me instead of inventing one silently.

Fix it yourself

Follow the repair

  1. Find the .map() the warning points at. The console warning names the component that rendered the list. Open it and find the .map() producing the items. The key belongs on the element that callback returns — its outermost element — not on anything nested inside it.
  2. Set the key to the item's real, stable ID. Your data almost always carries one already: a database row ID, a Supabase id, a slug, an SKU. Use it directly:
    {posts.map((post) => (
      <PostCard key={post.id} post={post} />
    ))}
    Two rules make a key valid: it is unique among the siblings in that list, and it is the same for that item every time the list renders.
  3. Stop using the array index for lists that can change. key={index} looks harmless and silences nothing — React warns about missing keys, and index keys pass the warning while keeping the bug. The index labels a position, so when the list is filtered, sorted, prepended to, or an item is deleted, React reuses the row and its state for whichever item now occupies that position. Reserve index keys for lists that are guaranteed static, and prefer real IDs everywhere else out of habit.
  4. If the data has no ID, use a genuinely unique field — or add one. A slug, an email address, a username — any field that is unique per item works, and combining two fields (key={a.first + '-' + a.last}) is fine when neither is unique alone. What never works: Math.random() or Date.now(). Those generate a fresh key on every render, which tells React every item is brand new each time — worse than the warning you started with, because all row state resets constantly.
  5. Check for duplicates hiding in the data. If you set proper ID keys and the warning or the glitches persist, log the list of IDs and look for repeats. Duplicates sneak in when two fetches append to the same list, when results from two categories are merged, or when test data was seeded twice. Dedupe at the source rather than papering over it in the render.
  6. For multi-element items, put the key on a Fragment. When one item renders as several top-level elements — a term and its definition, a row and its detail panel — wrap them in a Fragment and key the Fragment:
    {items.map((item) => (
      <Fragment key={item.id}>
        <dt>{item.term}</dt>
        <dd>{item.definition}</dd>
      </Fragment>
    ))}
    Use the long <Fragment> form; the <> shorthand cannot accept a key.
Why this happensOptional explanation

When a list changes — an item added, deleted, filtered, or re-sorted — React has to work out which old row corresponds to which new row so it can update the page efficiently and keep each row's internal state (typed text, open/closed toggles, checkbox states) attached to the right item. It does that matching with the key prop: a label you attach to each item that is unique among its siblings and never changes for that item.

Without keys, React falls back to matching by position: first row to first row, second to second. Delete the first item and every row below it inherits the previous row's state — the checkbox you ticked on one post suddenly sits on another, a half-typed comment appears under the wrong entry. Using the array index as the key has exactly the same problem, because the index is the position. And keys that repeat — two items with the same ID — break the matching from the other direction. The warning is React telling you the labelling is missing or unreliable before those glitches start.

  • No key at all: the map returns elements without a key prop — the most common version.
  • Index as key: key={index} labels items by position, so any reorder or deletion mislabels them.
  • Duplicate keys: the data itself contains the same ID twice, often after merging two lists or fetching twice.
  • Key on the wrong element: the key sits on a child inside the item instead of the element the map returns.

Quick answers

It's only a warning and my app works. Can I ignore it?

The app runs, but React cannot reliably tell items apart when the list changes, so updates can land on the wrong row, inputs can lose focus or hold the wrong text, and row state can attach to the wrong item. It is a one-line fix for a whole class of confusing bugs — worth doing the moment you see it.

Why is the array index such a bad key if nothing reorders?

If the list is truly static — never filtered, sorted, added to, or removed from — index keys mostly behave. The trouble starts the first time the list changes: the index is the position, not the item, so React reuses the row in that position and its state for a different item. Real IDs cost nothing extra, so use them from the start.

Where exactly does the key go?

On the single outermost element that the map callback returns — the top element of each item — not on elements nested inside it. If each item returns several top-level elements, wrap them in a Fragment and put the key on the Fragment.

One fix, every Friday.

One real error, explained in plain English, with the fix — in your inbox every Friday. No spam, no jargon, unsubscribe anytime.