Skip to content

Fix: React Warning: Each child in a list should have a unique "key" prop

FixDevs ·

Quick Answer

How to fix the React unique key prop warning caused by missing keys in lists, duplicate keys, index keys, nested maps, and dynamic list rendering issues.

The Error

You render a list in React and see in the browser console:

Warning: Each child in a list should have a unique "key" prop.

Check the render method of `UserList`.

Or:

Warning: Each child in a list should have a unique "key" prop.
See https://reactjs.org/link/warning-keys for more information.

React needs a unique key prop on every element rendered inside an array (usually from .map()). Without keys, React cannot efficiently track which items changed, were added, or were removed.

Why This Happens

When React re-renders a list, it needs to match old elements to new elements. Without keys, React compares elements by position — if you insert an item at the beginning, React thinks every item changed because they all shifted down. This causes unnecessary re-renders and can break component state.

Keys give React a stable identity for each list item. When an item moves, React matches it by key and efficiently updates only what changed.

This warning appears when:

  • No key prop is provided on list elements.
  • Duplicate keys exist — two elements have the same key.
  • Keys are not stable — they change between renders (e.g., using Math.random()).

Fix 1: Add a Unique Key from Your Data

The best key is a unique identifier from your data — an ID, slug, or other stable unique field:

Broken:

function UserList({ users }) {
  return (
    <ul>
      {users.map(user => (
        <li>{user.name}</li>  // Missing key!
      ))}
    </ul>
  );
}

Fixed:

function UserList({ users }) {
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

The key must be:

  • Unique among siblings (not globally unique, just within the list)
  • Stable — the same item always gets the same key
  • A string or number — not an object

Pro Tip: The key prop is consumed by React internally and is NOT passed down to the component as a prop. If you need the ID inside the component, pass it as a separate prop: <UserCard key={user.id} userId={user.id} />.

Fix 2: Avoid Using Array Index as Key

Using the array index as a key is a common but often problematic workaround:

{users.map((user, index) => (
  <li key={index}>{user.name}</li>
))}

This silences the warning but causes bugs when:

  • Items are reordered (drag-and-drop, sorting)
  • Items are inserted or removed from the middle
  • Items have internal state (input values, expanded/collapsed state)

When index keys are OK:

  • The list is static and never reorders
  • Items have no internal state
  • Items are never inserted or removed from the middle

When index keys cause bugs:

// Each item has an input with internal state
{items.map((item, index) => (
  <div key={index}>
    <input defaultValue={item.name} />
    <button onClick={() => removeItem(index)}>Delete</button>
  </div>
))}

If you delete item 1, React reassigns keys: what was key=2 becomes key=1. React reuses the DOM element for key=1 — including its input state. The input now shows the wrong value.

Fix: Use a stable unique identifier. If your data has no IDs, generate them when the data is created:

const [items, setItems] = useState(
  initialItems.map(item => ({ ...item, id: crypto.randomUUID() }))
);

Fix 3: Fix Keys on Component Lists

The key goes on the outermost element returned by .map(), not on an inner element:

Broken — key on inner element:

{users.map(user => (
  <div>
    <span key={user.id}>{user.name}</span>  // Wrong — key is on inner element
  </div>
))}

Fixed — key on the outer element:

{users.map(user => (
  <div key={user.id}>
    <span>{user.name}</span>
  </div>
))}

With fragments:

{users.map(user => (
  <React.Fragment key={user.id}>
    <dt>{user.name}</dt>
    <dd>{user.email}</dd>
  </React.Fragment>
))}

Note: The shorthand fragment syntax <>...</> does not accept a key prop. You must use <React.Fragment key={...}> when you need a key on a fragment.

Fix 4: Fix Duplicate Keys

Two items with the same key confuse React. It logs a separate warning:

Warning: Encountered two children with the same key, `user-1`.

Common cause — non-unique field used as key:

// If multiple users share the same name, keys are duplicated
{users.map(user => (
  <li key={user.name}>{user.name}</li>
))}

Fixed — use a truly unique field:

{users.map(user => (
  <li key={user.id}>{user.name}</li>
))}

If no unique field exists, combine fields:

{users.map((user, index) => (
  <li key={`${user.name}-${user.email}-${index}`}>{user.name}</li>
))}

Fix 5: Fix Keys in Nested Maps

When you have nested .map() calls, each level needs its own keys:

{categories.map(category => (
  <div key={category.id}>
    <h2>{category.name}</h2>
    <ul>
      {category.items.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  </div>
))}

Keys only need to be unique among siblings — items in different categories can have the same key without issues.

Fix 6: Fix Keys with Conditional Rendering

When conditionally rendering list items, make sure keys are consistent:

Broken — key changes based on condition:

{items.map((item, index) => (
  <div key={item.isSpecial ? `special-${index}` : index}>
    {item.name}
  </div>
))}

If isSpecial changes, the key changes, and React unmounts and remounts the element — losing all state.

Fixed — use a stable key regardless of state:

{items.map(item => (
  <div key={item.id}>
    {item.name}
  </div>
))}

Common Mistake: Using Math.random() or Date.now() as keys. These generate new keys on every render, forcing React to destroy and recreate every element in the list. This destroys all internal state and kills performance:

// NEVER DO THIS:
<li key={Math.random()}>{item.name}</li>

Fix 7: Fix Keys When Rendering Arrays from APIs

API responses often have IDs that make perfect keys:

function PostList() {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    fetch("/api/posts")
      .then(res => res.json())
      .then(setPosts);
  }, []);

  return (
    <div>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </div>
  );
}

If the API does not return IDs, ask the backend team to add them. IDs are almost always available in database-backed APIs.

If the API response is not what you expect, see Fix: Objects are not valid as a React child for rendering issues with API data.

For data fetching issues with useEffect, see Fix: React useEffect infinite loop.

Fix 8: Understand When Keys Reset State

Keys are not just for lists. You can use them to force a component to remount:

// Changes to userId reset the entire Profile component
<Profile key={userId} userId={userId} />

When the key changes, React unmounts the old component and mounts a new one. This resets all internal state, including useState, useRef, and uncontrolled form inputs.

This is useful when you want a component to “start fresh” when its data source changes, instead of trying to update in place.

Still Not Working?

If the warning persists after adding keys:

Check that the key is on the right element. React expects the key on the element returned by .map(), not on a nested child.

Check for arrays of arrays. If you have [[item1, item2], [item3, item4]], each inner array’s items need keys too.

Check for string children with .split(). Splitting a string into an array and rendering it triggers the key warning:

// Triggers warning:
{"Hello World".split(" ").map(word => <span>{word} </span>)}

// Fixed:
{"Hello World".split(" ").map((word, i) => <span key={i}>{word} </span>)}

Index keys are fine here because the split result is always in the same order.

Check for React.Children.toArray(). If you dynamically construct children, wrap them:

const children = React.Children.toArray(dynamicChildren);

This automatically assigns keys to children that do not have them.

For performance issues caused by large lists re-rendering, see Fix: React too many re-renders. For hook-related issues in list rendering, see Fix: React hooks called conditionally.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles