# React: “Each child in a list should have a unique "key" prop”

> React warns: Each child in a list should have a unique key prop. Which key to use, why the array index and Math.random() cause bugs, and fragments.

Canonical: https://app369.com/fix/react-each-child-unique-key/
Last updated: 2026-11-07

The console shows Each child in a list should have a unique key prop, usually next to a .map() that renders a list. The page may look fine, but without stable keys React can mix up items when the list is sorted, filtered or edited: inputs keep the wrong text, the wrong row animates, or state jumps to another item.

**Short answer**

Give the outermost element returned from .map() a key that is unique among its siblings and stays the same between renders, usually the item’s id from the database. Do not use Math.random() and avoid the array index when the list can change order. If each item renders several elements, wrap them in <Fragment key={item.id}>, because the short <>...</> form cannot take a key.

## **Why React needs the key, and the fix**

1. 01 ### **The element returned from .map() has no key**
  > “Keys tell React which array item each component corresponds to, so that it can match them up later.” [**React, Rendering Lists**](https://react.dev/learn/rendering-lists) React uses the key to tell which item is which between renders. The key goes on the outermost element inside the map callback, not on a child inside it and not on the list wrapper.```
// warns: no key
{todos.map((todo) => <TodoItem todo={todo} />)}

// works: stable id from the data
{todos.map((todo) => <TodoItem key={todo.id} todo={todo} />)}
```
2. 02 ### **Use an id that comes with the data**
  > “Data from a database: If your data is coming from a database, you can use the database keys/IDs, which are unique by nature.” [**React, Rendering Lists**](https://react.dev/learn/rendering-lists) Rows from a database already have a unique id; use it. For items created in the browser, give each one an id when you create it, with crypto.randomUUID() or a counter, and store it with the item. Keys only need to be unique within the same list.
3. 03 ### **The index or Math.random() as the key**
  > “This will cause keys to never match up between renders, leading to all your components and DOM being recreated every time.” [**React, Rendering Lists**](https://react.dev/learn/rendering-lists) The array index silences the warning but breaks when items are inserted, removed or reordered, because a different item takes over the same key. Math.random() or crypto.randomUUID() called during render is worse: every render gets new keys, so React rebuilds every item and drops their state.
4. 04 ### **Several elements per item: use Fragment with a key**
  > “The short <>...</> Fragment syntax won’t let you pass a key” [**React, Rendering Lists**](https://react.dev/learn/rendering-lists) When each item renders more than one element side by side, wrap them in Fragment and put the key there. The short <>...</> syntax has no place for a key.```
import { Fragment } from 'react';

{people.map((person) => (
  <Fragment key={person.id}>
    <h2>{person.name}</h2>
    <p>{person.bio}</p>
  </Fragment>
))}
```

Checked against the vendor’s docs on November 7, 2026: [React: Rendering Lists (keeping list items in order with key)](https://react.dev/learn/rendering-lists).

## **Rather hand it over?**

Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.

- [from $450**App Fix Ticket**One reproducible bug in an existing mobile or web app, diagnosed and fixed.**See scope and start**](https://app369.com/fixed-price/flutter-firebase-bug-fix/)
- [from $450**AI-Built App Rescue**A Lovable, Bolt or Cursor app checked end to end in 3 days, then its worst problems fixed at a fixed price.****No issues worth fixing, no charge.**** If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way. This applies to the first 10 Launch Checks bought after October 30, 2026.**See scope and start**](https://app369.com/fixed-price/ai-app-rescue/)

Related: [**React “Invalid hook call”**](https://app369.com/fix/react-invalid-hook-call/) · [**React “Too many re-renders”**](https://app369.com/fix/react-too-many-re-renders/) · [**Next.js hydration error**](https://app369.com/fix/nextjs-hydration-error/)

Questions

## **More on this error**

<details>

<summary>**Is the index ever fine as a key? **+</summary>

React uses the index itself when you give no key, and its docs advise against it: when items are inserted, removed or reordered, a different item takes over the same key. Use an id from the data.

</details>

<details>

<summary>**Why can’t my component read props.key? **+</summary>

React keeps the key for itself and does not pass it to the component. If the component needs the id, pass it again as a separate prop, for example key={id} userId={id}.

</details>

<details>

<summary>**Can you fix it for me? **+</summary>

If one bug is the whole problem, the App Fix Ticket covers it: a written diagnosis first, then the fix. If the app has more going on, the Launch Check is a written check of the whole app. Each issue found is rated critical, high, medium or low, with where it is and how it was confirmed. No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way. This applies to the first 10 Launch Checks bought after October 30, 2026.

</details>

A different error?

## **Describe it and get a written quote**

Send the error and what you were doing. You get a written scope, a fixed price and a timeline; Simon replies by email, usually within one business day.

[**Describe your problem **→](https://app369.com/contact/) [**All fixes**](https://app369.com/fix/)