Lists and Keys in React

Almost every real application needs to display collections of data — a list of products, a feed of posts, search results, a table of users. Rather than writing out repetitive JSX for each item by hand, React lets you dynamically render lists from arrays of data. Along the way, you’ll run into a special prop called key, which trips up a lot of beginners at first but is actually solving a very important problem.

Rendering a Basic List

The most common way to render a list in React is using JavaScript’s built-in .map() array method inside your JSX.

jsx
function FruitList() {
  const fruits = ["Apple", "Banana", "Mango", "Orange"]

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

Here’s what’s happening:

  • fruits.map((fruit) => ...) loops over every item in the fruits array
  • For each item, it returns a piece of JSX (<li>{fruit}</li>)
  • The result is an array of JSX elements, which React knows how to render directly inside <ul>

If you run this exact code, though, you’ll notice a warning in your browser console:

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

This is where key comes in.

What Is the key Prop?

The key prop is a special attribute you add to each item in a list, giving React a stable, unique identifier for that specific list item.

jsx
function FruitList() {
  const fruits = ["Apple", "Banana", "Mango", "Orange"]

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

Notice key={index} — we’re using each item’s position in the array as its key. This resolves the warning, but as we’ll see shortly, using the array index isn’t always the best choice.

Why Does React Need Keys At All?

To understand why keys matter, you need to understand a bit about how React updates the DOM.

Whenever your component’s state or props change, React doesn’t throw away the entire DOM and rebuild it from scratch — that would be extremely slow. Instead, React uses a process called reconciliation: it compares the new list of elements to the previous one and figures out the minimum number of changes needed to update the actual browser DOM.

When you render a list, key is what allows React to answer the question: “Is this the same item as before, just possibly in a different position or with different content — or is this a completely new item?”

Without keys, React has to guess based on position alone, which can lead to subtle bugs — especially when items are added, removed, or reordered.

A Practical Example of Why Keys Matter

Imagine a to-do list where each item has its own checkbox state:

jsx
function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: "Buy groceries" },
    { id: 2, text: "Clean the house" },
    { id: 3, text: "Walk the dog" },
  ])

  const removeFirst = () => {
    setTodos(todos.slice(1))
  }

  return (
    <div>
      <button onClick={removeFirst}>Remove First Item</button>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            <input type="checkbox" /> {todo.text}
          </li>
        ))}
      </ul>
    </div>
  )
}

If you used the array index as the key here instead of todo.id, and then removed the first item, React might get confused about which checkbox belongs to which item. Since indexes shift when an item is removed (item at index 1 becomes index 0, and so on), React could end up reusing the DOM node for “Buy groceries” — checkbox state and all — for what is now “Clean the house.” The checked/unchecked state would appear to “stick” to the wrong item.

Using a stable, unique identifier like todo.id avoids this problem entirely, because React can correctly track each item regardless of its position in the array.

Best Practices for Choosing Keys

✅ Use a Unique, Stable ID When Available

If your data comes from a database or API, it almost always has a unique identifier (like id). Always prefer this over the array index:

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

⚠️ Use Array Index Only as a Last Resort

Using the index as a key is acceptable only when:

  • The list is static and will never be reordered, filtered, or have items added/removed in the middle
  • The items have no unique ID of their own
  • The list will never change during the component’s lifetime
jsx
{staticColors.map((color, index) => (
  <li key={index}>{color}</li>
))}

For anything involving dynamic lists — sorting, filtering, adding, or removing items — avoid the index and generate or use a real unique ID instead.

❌ Never Use Random Values Generated on Every Render

jsx
<li key={Math.random()}>{item}</li>   // ❌ Never do this

Since Math.random() generates a brand-new value on every single render, React will think every item is new each time the component re-renders, causing it to throw away and recreate all the DOM nodes unnecessarily. This defeats the entire purpose of keys and can cause noticeable performance issues along with UI glitches like lost focus or flickering.

Keys Must Be Unique Among Siblings Only

Keys don’t need to be globally unique across your entire application — they only need to be unique among siblings within the same list.

jsx
function App() {
  return (
    <div>
      <ul>
        <li key="1">Item A</li>
        <li key="2">Item B</li>
      </ul>
      <ul>
        <li key="1">Item X</li> {/* This is fine — different list */}
        <li key="2">Item Y</li>
      </ul>
    </div>
  )
}

Since these two <ul> elements represent separate lists, reusing key="1" and key="2" in each one causes no conflict.

Rendering Lists of Components (Not Just HTML Elements)

The same rules apply when you’re rendering a list of custom components instead of plain HTML tags.

jsx
function ProductCard({ name, price }) {
  return (
    <div className="card">
      <h3>{name}</h3>
      <p>${price}</p>
    </div>
  )
}

function ProductList({ products }) {
  return (
    <div className="grid">
      {products.map((product) => (
        <ProductCard key={product.id} name={product.name} price={product.price} />
      ))}
    </div>
  )
}

An important detail: the key prop must be placed on the outermost element returned from .map() — in this case, on <ProductCard> itself, not on some element buried inside the ProductCard component. React uses key specifically at the point where the list is generated, so placing it anywhere else won’t work as expected.

Filtering Lists Before Rendering

You’ll often need to combine .filter() with .map() to show only a subset of your data:

jsx
function ProductList({ products }) {
  const inStockProducts = products.filter((product) => product.inStock)

  return (
    <div>
      {inStockProducts.map((product) => (
        <ProductCard key={product.id} name={product.name} price={product.price} />
      ))}
    </div>
  )
}

This is a very common pattern: filter the data down to what you actually want to display, then map over the filtered result to generate JSX.

Common Mistakes Beginners Make

  1. Forgetting the key prop entirely, resulting in a console warning and potential rendering bugs.
  2. Using the array index as a key for dynamic lists, causing state to get mismatched when items are reordered or removed.
  3. Using Math.random() or generating a new key on every render, which destroys React’s ability to efficiently reuse DOM nodes.
  4. Placing the key prop on the wrong element — it must go on the direct result of .map(), not on a nested child.
  5. Forgetting that keys are just for React’s internal use — trying to access props.key inside a component doesn’t work, since key is a special reserved prop that React doesn’t pass down.

Recap

In this post, you learned:

  • How to render dynamic lists in React using .map()
  • Why React requires a key prop for list items, and how it relates to React’s reconciliation process
  • Why unique, stable IDs make better keys than array indexes for dynamic lists
  • Why keys only need to be unique among siblings, not across the whole app
  • How to combine .filter() and .map() to render a subset of your data

With this post, you’ve now completed the beginner fundamentals of React — components, JSX, props, state, events, conditional rendering, and lists. In the next post, we’ll move into intermediate territory and dig into Forms, covering the difference between controlled and uncontrolled components, and how to properly manage user input at scale.

Share.
Leave A Reply

Exit mobile version