EduFlow
React Fundamentals
0%

Section 2 · Components & Props

Rendering Lists with Keys

22 min3 min readCode playground

Lesson at a glance

Render lists from arrays with map, filter and sort them without mutating data, and choose keys that keep each item’s identity and state correct between renders.

  1. 1From data to elements with map
  2. 2What keys are for
  3. 3Choosing a key
  4. 4Keys on components and fragments

No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.

The Watchlist is a list. So is a feed, a table of orders, a set of search results, a menu. Most UI is lists of things built from arrays of data, and React’s approach is the one you’d guess from JavaScript: transform the array of data into an array of elements.

From data to elements with map

src/components/MovieList.jsx
export default function MovieList({ movies }) {
  return (
    <ul>
      {movies.map((movie) => (
        <li key={movie.id}>
          {movie.title} ({movie.year})
        </li>
      ))}
    </ul>
  );
}

map returns an array of <li> elements, and React renders arrays by rendering each item in order. There’s no special loop syntax to learn; it’s the array method you already know.

Filtering and sorting are the same idea: prepare the array, then map it. Do it during render, from the data you have. The visible list is derived, so it doesn’t need to be state.

JavaScript
const movies = [
  { id: 'a1', title: 'Arrival', year: 2016, watched: true },
  { id: 'p2', title: 'Past Lives', year: 2023, watched: false },
  { id: 'd3', title: 'Dune: Part Two', year: 2024, watched: false },
];

const toWatch = movies
  .filter((m) => !m.watched)
  .toSorted((a, b) => b.year - a.year);

console.log(toWatch.map((m) => m.title)); // ["Dune: Part Two", "Past Lives"]
console.log(movies[0].title);             // Arrival (the original order is untouched)

toSorted returns a sorted copy and is supported in every current browser. The older sort reorders the array in place. Called on state or props, that silently mutates data you don’t own. If you need to support older browsers, copy first: [...movies].sort(…).

What keys are for

Every element in a list needs a key prop, and React warns in the console when one is missing. The reason becomes clear when the list changes.

When React re-renders a list, it has the old array of elements and the new one. It needs to match them up: which new element is the same item as which old one? That matters because each item may have state, such as a half-typed note in an input, a focused button or an open menu, and that state must stay with the right item.

The key is the item’s name tag. React matches old and new elements by key. Same key: it’s the same item, so React keeps its DOM and state and updates what changed. New key: a new item, so React creates it. Missing key: the item was removed, so React destroys it.

Before: Arrival has a note. After adding Dune at the top with index keys, key 0 now points to Dune, so the note jumps to Dune. With id keys, the note stays with Arrival. Before 0 · Arrival · "Loved it" 1 · Past Lives key={index} 0 · Dune · "Loved it" 1 · Arrival 2 · Past Lives key={movie.id} d3 · Dune a1 · Arrival · "Loved it" p2 · Past Lives Same key, same row: state follows the key Index keys follow position; id keys follow the movie
Index keys versus id keys after inserting at the top

Choosing a key

A good key is unique among siblings and stable: the same item has the same key on every render.

  • Use an id from your data. Database rows and API objects already have one.
  • Create ids when items are created. For movies added through a form, generate the id in the handler that adds them: { id: crypto.randomUUID(), title, year }. It’s stored on the object and never changes.
  • Never generate keys during render. key={Math.random()} or key={crypto.randomUUID()} inside map produces new keys every render. React treats every row as brand new, tears down the DOM and loses input focus and state each time you type.
  • Don’t use content that isn’t unique. Titles repeat; there are several films called “Dune”.

Keys only need to be unique among siblings in the same list. Two different lists can use the same ids.

Keys on components and fragments

The key goes on the outermost element returned from the map callback. When that’s a component, put it on the component:

JSX
{movies.map((movie) => (
  <MovieItem key={movie.id} movie={movie} />
))}

MovieItem can’t read key; React uses it and doesn’t pass it down. If the component needs the id, pass it separately as a prop.

When each item renders several siblings without a wrapper, use the long form of a fragment, which accepts a key:

JSX
import { Fragment } from 'react';

{movies.map((movie) => (
  <Fragment key={movie.id}>
    <dt>{movie.title}</dt>
    <dd>{movie.year}</dd>
  </Fragment>
))}

The short <>…</> syntax can’t take a key.

You can now render any list correctly. That completes the static Watchlist. In the next section, it starts to move: state lets the list change when the user clicks, types and submits.

Key takeaways

  • Turn an array of data into an array of elements with map, and put the key on the outermost element the callback returns.
  • A key tells React which item is which between renders, so it must be unique among siblings and stable over time.
  • Use an id from your data as the key; create ids when items are created, never during render.
  • Index keys break when the list can be reordered, filtered or inserted into, because state sticks to the position instead of the item.
  • Filter and sort copies of your data while rendering; sort changes the array in place, so use toSorted or sort a copy.

Further reading

Check your understanding

4 questions

Question 1 of 4

Where does the key go?

JSX
function MovieList({ movies }) {
  return (
    <ul>
      {movies.map((movie) => (
        <MovieItem movie={movie} />
      ))}
    </ul>
  );
}
Question 2 of 4

Each movie row has a text input for a note. You type “Loved it” next to Arrival, then add a new movie to the top of the list. With key={index}, where does “Loved it” end up?

Question 3 of 4

Which key is a good choice for movies the user adds through a form?

Question 4 of 4

movies comes from state. What’s the problem with const sorted = movies.sort((a, b) => b.year - a.year); in the render code?

Practice

Code playground

Keys that keep notes in place

Each movie row has a note input. Two problems:

  1. The list should show movies newest first (by year). Sort a copy during render; don’t change the movies state.
  2. Type a note next to Arrival, then click Add Dune: Part Two. The note jumps to the wrong movie. Fix the key so each note stays with its movie.

Keep the markup the same: one <li> per movie with a <span> (title and year) and an <input>.

import { useState } from 'react';

const INITIAL = [
  { id: 'a1', title: 'Arrival', year: 2016 },
  { id: 'p2', title: 'Past Lives', year: 2023 },
];

export default function App() {
  const [movies, setMovies] = useState(INITIAL);

  function addDune() {
    setMovies([{ id: 'd3', title: 'Dune: Part Two', year: 2024 }, ...movies]);
  }

  // 1. Show the movies newest first, without changing `movies` itself.
  // 2. Fix the key so each note stays with its movie.
  return (
    <main>
      <button onClick={addDune}>Add Dune: Part Two</button>
      <ul>
        {movies.map((movie, index) => (
          <li key={index}>
            <span>{movie.title} ({movie.year})</span>
            <input aria-label="Note" placeholder="Note" />
          </li>
        ))}
      </ul>
    </main>
  );
}
Loading the editor…

Ctrl/⌘ + Enter to run · Runs in a sandbox in your browser. Nothing leaves your device.

Preview

Console

(no output)

Checks

  • Movies appear newest first: Past Lives, then Arrival
  • After adding Dune, Arrival’s note is still next to Arrival
  • Dune: Part Two (2024) appears first after it is added