React Lists

Rendering lists is one of the most frequent tasks in React. You almost always have some data (products, users, messages) and you need to turn it into a list of elements on screen.

React does not have a special list component or template syntax for this. You use plain JavaScript, specifically the map() method, to transform an array of data into an array of JSX elements.

Rendering a list with map()

function FruitList() {
  const fruits = ['Apple', 'Banana', 'Cherry', 'Mango'];

  return (
    <ul>
      {fruits.map((fruit) => (
        <li>{fruit}</li>
      ))}
    </ul>
  );
}

This works, but React will show a warning in the console: “Each child in a list should have a unique ‘key’ prop.” That warning matters.

The key prop

When React renders a list, it needs a way to tell items apart. If the list changes (an item is added, removed, or reordered), React needs to know which DOM element corresponds to which piece of data. That is what key is for.

A key is a unique string or number that you add to each item in a mapped list:

function FruitList() {
  const fruits = [
    { id: 1, name: 'Apple' },
    { id: 2, name: 'Banana' },
    { id: 3, name: 'Cherry' },
  ];

  return (
    <ul>
      {fruits.map((fruit) => (
        <li key={fruit.id}>{fruit.name}</li>
      ))}
    </ul>
  );
}

The key is not rendered to the DOM. It is only used internally by React.

What makes a good key

A good key is unique within the list and stable across renders. In practice, this means using an ID from your data.

The index-as-key problem

You might be tempted to use the array index as a key:

{fruits.map((fruit, index) => (
  <li key={index}>{fruit.name}</li>
))}

This silences the warning, but it can cause bugs when the list changes. Here is why: if you remove the first item from the list, all the indexes shift. React sees that key={0} now points to a different item, but it reuses the existing DOM element instead of creating a new one. The result can be mismatched UI state, especially with inputs or animations.

Use the array index as a key only when:

  • The list is read-only and will never be reordered
  • The items have no stable IDs

For any list that can be edited, filtered, reordered, or added to, use a proper unique ID.

Building a reusable list component

In real apps, you usually build a component that accepts an array as a prop:

function ProductList({ products }) {
  return (
    <ul className="product-list">
      {products.map((product) => (
        <li key={product.id} className="product-item">
          <strong>{product.name}</strong>: ${product.price}
        </li>
      ))}
    </ul>
  );
}

// Used in a parent:
function App() {
  const products = [
    { id: 1, name: 'Keyboard', price: 79 },
    { id: 2, name: 'Mouse', price: 49 },
    { id: 3, name: 'Monitor', price: 299 },
  ];

  return <ProductList products={products} />;
}

You can also split out the individual item into its own component:

function ProductItem({ product }) {
  return (
    <li className="product-item">
      <strong>{product.name}</strong>: ${product.price}
    </li>
  );
}

function ProductList({ products }) {
  return (
    <ul className="product-list">
      {products.map((product) => (
        <ProductItem key={product.id} product={product} />
      ))}
    </ul>
  );
}

Note: the key goes on the outermost element returned from map(), which here is <ProductItem>, not on the <li> inside ProductItem.

Rendering an empty state

Always handle the case where the list is empty:

function ProductList({ products }) {
  if (products.length === 0) {
    return <p>No products found.</p>;
  }

  return (
    <ul>
      {products.map((product) => (
        <ProductItem key={product.id} product={product} />
      ))}
    </ul>
  );
}

This is a much better experience than rendering an empty <ul> or showing nothing at all.

You can also combine this with a loading state if the data is fetched from an API:

function ProductList({ products, loading }) {
  if (loading) return <p>Loading...</p>;
  if (products.length === 0) return <p>No products found.</p>;

  return (
    <ul>
      {products.map((product) => (
        <ProductItem key={product.id} product={product} />
      ))}
    </ul>
  );
}

Common mistakes

Missing the key prop.

React will render the list but log a warning. More importantly, the list may not update correctly when items are added, removed, or reordered. Always add a key.

Using array index as key on a mutable list.

As described above, index keys cause subtle bugs when items move. Use a unique ID from your data instead.

Returning multiple root elements from map without a key.

If a single list item needs to render more than one element, wrap them in a Fragment with a key:

{items.map((item) => (
  <React.Fragment key={item.id}>
    <dt>{item.term}</dt>
    <dd>{item.definition}</dd>
  </React.Fragment>
))}

You cannot use the shorthand <> here because shorthand fragments do not support the key prop.

What to learn next