Code Review Videos > How I Fixed > How I Fixed: Warning: Each child in a list should have a unique “key” prop.

How I Fixed: Warning: Each child in a list should have a unique “key” prop.

You may well have come across the warning in React where you are rendering out a list of things, and you forget to give each element a unique key. If you do that, you get the following showing your browser’s console: Warning: Each child in a list should have a unique "key" prop.

In this post we will look at a way to fix that and how to fix a slightly more complicated yet still real world problem where the most common fix won’t work. This is when you have to return multiple items such as when we have a definition list, or in this case, more than one li for a given array element: a breadcrumb, and a separator.

Here’s an example of this in the browser:

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

Right, so there’s a very easy fix to this, and exactly how you fix it is right there in the error message.

Let’s look at the code:

import * as React from "react";

export default function Things() {
  const myThings = ["lorum", "ipsum", "plinko", "plonko"];

  return (
    <ul>
      {myThings.map((thing) => (
        <li>{thing}</li>
      ))}
    </ul>
  );
}Code language: TypeScript (typescript)

The Easiest Fix

If your list is really straightforward, the fix is also very easy to apply:

import * as React from "react";

export default function Things() {
  const myThings = ["lorum", "ipsum", "plinko", "plonko"];

  return (
    <ul>
      {myThings.map((thing) => (
        <li key={thing}>{thing}</li>
      ))}
    </ul>
  );
}
Code language: TypeScript (typescript)

That’s how you fix it, in the most basic sense.

You need some unique value for each element in the list of things you want to render out.

The Second Easiest Fix

If your list has duplicate values, then the above won’t directly solve the problem. Each key prop has to be unique in the current list.

import * as React from "react";

export default function Things() {
  const myThings = ["plinko", "planko", "plinko", "plonko"];

  return (
    <ul>
      {myThings.map((thing) => (
        <li key={thing}>{thing}</li>
      ))}
    </ul>
  );
}
Code language: TypeScript (typescript)

Same code, but now we have two instances of plinko, so we get:

react Warning: Encountered two children with the same key

Where the error is:

Warning: Encountered two children with the same key, plinko. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.

Damn you React!

If your list is static then you can get away with using the index from the .map function. The second argument of the callback used by each pass through the array will be a numerical index:

// Arrow function
map((element) => { /* … */ })
map((element, index) => { /* … */ })
map((element, index, array) => { /* … */ })Code language: JavaScript (javascript)

And so we have a way to make each rendered element be unique:

import * as React from "react";

export default function Things() {
  const myThings = ["plinko", "planko", "plinko", "plonko"];

  return (
    <ul>
      {myThings.map((thing, index) => (
        <li key={`${thing}-${index}`}>{thing}</li>
      ))}
    </ul>
  );
}
Code language: TypeScript (typescript)

In this case, the key is created by concatenating the “thing” string with the “index” number using a template string. Even though we have two instances of plinko, by combining the string with the position / index of where that string is in the myThings array, we get two different values:

  • plinko-0
  • plinko-2

Remembering that arrays are zero indexed, of course.

If your list allows adding, editing, re-ordering, or deleting, this approach won’t work. At least not reliably, or in a performant fashion.

For that situation you need some unique identifier that is not tied to the element’s current position in the array.

Two really good resources on this are:

Real World Issue: Breadcrumb With Separators

All of the above is good, but I hit on a variation of this when building out a breadcrumbs component. It’s similar to the above, but not quite the same problem.

Here’s the visual output:

react each child in a list should have a unique key prop with breadcrumbs example

The issue here is the breadcrumb output, which uses the following component:

import * as React from "react";
import Link from "next/link";

type BreadcrumbsProps = {
  active: {
    title: string;
  };
  parents?: { title: string; path: string }[];
};

export default function Breadcrumbs({
  active: { title },
  parents = [],
}: BreadcrumbsProps) {
  return (
    <nav className="bg-gray-50 w-full p-4 border-b-2">
      <ol className="list-reset flex">
        <li>
          <Link
            href="/"
            className="text-green-700 hover:text-green-900"
            title="Home"
          >
            Home
          </Link>
        </li>
        <li>
          <span className="text-gray-500 mx-2">{">"}</span>
        </li>
        {parents.length > 0 &&
          parents.map((parent) => {
            return (
              <>
                <li>
                  <Link
                    href={parent.path}
                    className="text-green-700 hover:text-green-900"
                    title={parent.title}
                  >
                    {parent.title}
                  </Link>
                </li>
                <li>
                  <span className="text-gray-500 mx-2">{">"}</span>
                </li>
              </>
            );
          })}
        <li className="text-gray-700 ">{title}</li>
      </ol>
    </nav>
  );
}Code language: TypeScript (typescript)

In this the first li element is a link to the homepage, and the second li element is a separator (>).

The remaining li elements are generated dynamically based on the parents prop.

For each parent in the array, a link is created with the title and path properties, followed by a separator. The active page’s title is displayed in the last li element.

Pretty standard Breadcrumbs. The homepage link is always shown, and the current page is displayed but isn’t a link. There can be zero or more pages in between.

But there’s a problem here (of course there is, or I wouldn’t be writing this!):

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

Check the render method of `Breadcrumbs`. See https://reactjs.org/link/warning-keys for more information.
    at Breadcrumbs (webpack-internal:///./lib/components/Breadcrumbs.tsx:15:19)
    at div
    at div
    at main
    at div
    at div
... other stuffCode language: JavaScript (javascript)

And so the problematic code is here:

{parents.length > 0 &&
  parents.map((parent) => {
    return (
      <>
        <li>
          <Link
            href={parent.path}
            className="text-green-700 hover:text-green-900"
            title={parent.title}
          >
            {parent.title}
          </Link>
        </li>
        <li>
          <span className="text-gray-500 mx-2">{">"}</span>
        </li>
      </>
    );
  })}Code language: TypeScript (typescript)

If we try to apply the “easy fix” from above, this doesn’t work:

{parents.length > 0 &&
  parents.map((parent) => {
    return (
      <>
        <li key={`${parent.path}-link`}>
          <Link
            href={parent.path}
            className="text-green-700 hover:text-green-900"
            title={parent.title}
          >
            {parent.title}
          </Link>
        </li>
        <li key={`${parent.path}-separator`}>
          <span className="text-gray-500 mx-2">{">"}</span>
        </li>
      </>
    );
  })}
Code language: JavaScript (javascript)

In this instance it’s not possible to combine the elements somehow such that we only have one common top level li element. Both the link and the separator need to be individual list items.

The parent.path here should always be unique. It represents a specific page on the site – there cannot be two different pages with the same path / URL.

However this still doesn’t work because even though these keys are unique, they are applied to the wrong element.

React needs us to provide the key prop on the parent element – line 4.

However, that is currently a Fragment.

What is a Fragment in React?

The <> element is called a React fragment or a shorthand syntax for the <React.Fragment> component. In React, components are usually required to have a single top-level element. However, in some cases, you might need to return multiple elements without adding an extra element to the DOM hierarchy. This is where React fragments come in handy.

React fragments allow you to group a list of children elements without adding an extra node to the DOM. It’s a lightweight syntax that can be used instead of creating a wrapper element just for the sake of returning multiple children.

The shorthand syntax of <> allows you to write a fragment without explicitly importing the React.Fragment component. You can use the shorthand syntax to wrap multiple JSX elements without adding an extra DOM element.

Adding a key to a React Fragment

Knowing that we need to move the key from the individual li elements up to their common parent, we hit another problem if we try to do this:

It’s a syntax error to put any kind of props on a shorthand Fragment. The issue is at line 32, and then that causes everything below that to appear to be wrong also.

From the browser this actually ends up with a seemingly unrelated error, but an error all the same:

nextjs error when using shorthand fragment with key prop

There are (at least) two ways to fix this.

Use Keyed Fragments

One way to fix this is to use a Keyed Fragment:

import * as React from "react";

// ...

{parents.length > 0 &&
  parents.map((parent) => (
    <React.Fragment key={parent.title}>
      <li>
        <Link
          href={parent.path}
          className="text-green-700 hover:text-green-900"
          title={parent.title}
        >
          {parent.title}
        </Link>
      </li>
      <li>
        <span className="text-gray-500 mx-2">{">"}</span>
      </li>
    </React.Fragment>
  ))}
Code language: TypeScript (typescript)

Keyed Fragments are named as such because the only allowed attribute that can go on a <React.Fragment> element is a key.

This solves our problem and should probably be the preferred way to solve this at a guess.

Use An Explicit Array

An explicit array? Is that one that arrives in a compromising fashion? Ho ho, a little humour with the code. I like it.

No, an explicit array, in this instance, is literally just an array we provide directly. As ever, code explains this far more clearly than words:

{parents.length > 0 &&
  parents.map((parent, i) => [
    <li key={`${parent.title}-${i}`}>
      <Link
        href={parent.path}
        className="text-green-700 hover:text-green-900"
        title={parent.title}
      >
        {parent.title}
      </Link>
    </li>,
    <li key={`${parent.title}-separator-${i}`}>
      <span className="text-gray-500 mx-2">{">"}</span>
    </li>,
  ])}
Code language: TypeScript (typescript)

The outcome here, from the front end, is identical.

We still get the list with both the parent page link, and the separator.

The map function on line 2 returns an array of JSX elements, which is why the square brackets [] are used to wrap them. This is because React expects a single parent element to be returned from a component, so by wrapping the array in brackets, we are creating a single parent element that contains all the breadcrumb items.

Because lines 3-11, and 12-14 are two separate array items, they need to be separated by a comma, just like any other array element. You can see that trailing comma on line 11.

Each element still needs a unique key, so we had to fall back to using the index / i as provided by the map function. This works fine here because this list is static.

Keyed Fragment vs Explicit Array?

Two ways to achieve the same result?

Yeah. Welcome to programming.

But which should you use?

Almost certainly: keyed fragments.

It’s strange that the array syntax is basically standard JavaScript. Any JS / TS dev looking at that should immediately understand it.

Only, it is an array of JSX elements. And that’s … kinda weird.

In either case we still need a unique key for each element, but with the Fragment we have a simpler key. And the Fragment looks like regular React stuff.

2 thoughts on “How I Fixed: Warning: Each child in a list should have a unique “key” prop.”

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.