LearnFrenzy
Sign in Take a free test

Components and Props · Lesson 7 of 31

Rendering Lists and Keys in React

6 min read Updated Nov 8, 2026 Saurabh Samir

Most apps display collections: products, messages, lessons, search results. In React you turn an array of data into an array of elements with map(). Each element also needs a key – a small detail that causes real bugs when it's wrong.

In this lesson

  • Rendering arrays with map()
  • Filtering and sorting before rendering
  • What keys are and how to choose them
  • The index-as-key bug, shown live

map() an array to elements

const lessons = [
  { id: "jsx", title: "JSX in React", minutes: 10 },
  { id: "props", title: "React Props", minutes: 12 },
  { id: "lists", title: "Lists and Keys", minutes: 9 },
];

export default function App() {
  return (
    <ol>
      {lessons.map((lesson) => (
        <li key={lesson.id}>
          {lesson.title} ({lesson.minutes} min)
        </li>
      ))}
    </ol>
  );
}

Result

1. JSX in React (10 min)
2. React Props (12 min)
3. Lists and Keys (9 min)

JSX can render an array of elements directly, so {lessons.map(…)} works anywhere an expression is allowed.

Filter and sort first

const products = [
  { sku: "KB-01", name: "Keyboard", price: 3499, inStock: true },
  { sku: "MS-02", name: "Mouse", price: 899, inStock: true },
  { sku: "MN-03", name: "Monitor", price: 12999, inStock: false },
  { sku: "HP-04", name: "Headphones", price: 2499, inStock: true },
];

export default function App() {
  const available = products
    .filter((p) => p.inStock)
    .toSorted((a, b) => a.price - b.price);   // toSorted returns a new array; never sort props in place

  return (
    <>
      <h2>In stock ({available.length})</h2>
      <ul>
        {available.map((p) => (
          <li key={p.sku}>{p.name} – ₹{p.price}</li>
        ))}
      </ul>
      <p>Total value: ₹{available.reduce((sum, p) => sum + p.price, 0)}</p>
    </>
  );
}

Result

In stock (3)
• Mouse – ₹899
• Headphones – ₹2499
• Keyboard – ₹3499
Total value: ₹6897

What keys do

When a list re-renders, React matches old and new items by key to decide which elements to keep, move, add or remove. Without keys React warns you in the console:

export default function App() {
  const tags = ["react", "hooks", "jsx"];
  return (
    <ul>
      {tags.map((t) => <li>{t}</li>)}
    </ul>
  );
}

Result and console

• react
• hooks
• jsx
console: error: Each child in a list should have a unique "key" prop.

Rules for keys

  • Unique among siblings (not globally).
  • Stable: the same item gets the same key on every render. Use an ID from your data – a database id, SKU or slug.
  • Never generate keys during render (Math.random(), crypto.randomUUID()): every render would look like a brand-new list.
  • The key goes on the outermost element returned by map; it isn't passed to the component as a prop.

Why the index is a bad key

Using the array index (key={i}) silences the warning but breaks when items are inserted, removed or reordered. Here each row has an uncontrolled input – type a note next to "Buy milk", then remove the first row:

import { useState } from "react";

const initial = [
  { id: 1, text: "Buy milk" },
  { id: 2, text: "Call Ravi" },
  { id: 3, text: "Pay rent" },
];

function TodoList({ useIndexKey }) {
  const [todos, setTodos] = useState(initial);
  return (
    <div>
      <h3>{useIndexKey ? "key={index}" : "key={todo.id}"}</h3>
      {todos.map((todo, index) => (
        <p key={useIndexKey ? index : todo.id}>
          {todo.text} <input aria-label={`note ${useIndexKey ? "A" : "B"}${index}`} />
        </p>
      ))}
      <button onClick={() => setTodos(todos.slice(1))}>Remove first {useIndexKey ? "A" : "B"}</button>
    </div>
  );
}

export default function App() {
  return (
    <>
      <TodoList useIndexKey />
      <TodoList useIndexKey={false} />
    </>
  );
}

Result

key={index}
Buy milk []
Call Ravi []
Pay rent []
[Remove first A]
key={todo.id}
Buy milk []
Call Ravi []
Pay rent []
[Remove first B]

› type “2 litres” into note A0
key={index}
Buy milk [2 litres]
Call Ravi []
Pay rent []
[Remove first A]
key={todo.id}
Buy milk []
Call Ravi []
Pay rent []
[Remove first B]

› type “2 litres” into note B0
key={index}
Buy milk [2 litres]
Call Ravi []
Pay rent []
[Remove first A]
key={todo.id}
Buy milk [2 litres]
Call Ravi []
Pay rent []
[Remove first B]

› click “Remove first A”
key={index}
Call Ravi [2 litres]
Pay rent []
[Remove first A]
key={todo.id}
Buy milk [2 litres]
Call Ravi []
Pay rent []
[Remove first B]

› click “Remove first B”
key={index}
Call Ravi [2 litres]
Pay rent []
[Remove first A]
key={todo.id}
Call Ravi []
Pay rent []
[Remove first B]

With index keys, React thinks item 0 still exists (key 0 is still there) and the last one was removed, so the note "2 litres" stays in the first row – now next to "Call Ravi". With id keys, React removes the right row along with its input.

Using the index is acceptable only when the list is static: never reordered, filtered or edited.

Extracting a list item component

function LessonRow({ title, done }) {
  return <li>{done ? "✔" : "○"} {title}</li>;
}

const lessons = [
  { slug: "react-jsx", title: "JSX in React", done: true },
  { slug: "react-props", title: "React Props", done: true },
  { slug: "react-lists-and-keys", title: "Lists and Keys", done: false },
];

export default function App() {
  const done = lessons.filter((l) => l.done).length;
  return (
    <>
      <p>Progress: {done}/{lessons.length}</p>
      <ul>
        {lessons.map((l) => (
          <LessonRow key={l.slug} title={l.title} done={l.done} />   // key on the component, not inside it
        ))}
      </ul>
    </>
  );
}

Result

Progress: 2/3
• ✔ JSX in React
• ✔ React Props
• ○ Lists and Keys

Practice

Try it yourself

  1. Group these students by city and render a heading per city with the names below it.

    Show solution
    const students = [
      { id: 1, name: "Asha", city: "Pune" },
      { id: 2, name: "Ravi", city: "Delhi" },
      { id: 3, name: "Meera", city: "Pune" },
    ];
    
    export default function App() {
      const byCity = Object.groupBy(students, (s) => s.city);
      return (
        <>
          {Object.entries(byCity).map(([city, people]) => (
            <section key={city}>
              <h3>{city}</h3>
              <ul>
                {people.map((p) => <li key={p.id}>{p.name}</li>)}
              </ul>
            </section>
          ))}
        </>
      );
    }

    Result

    Pune
    • Asha
    • Meera
    Delhi
    • Ravi

Quick check

What makes a good key?

A stable, unique id from the data, such as a database id or slug.

Why is key={Math.random()} wrong?

It changes every render, so React recreates every item and loses their state.

When is the index acceptable as a key?

Only for static lists that are never reordered, filtered or edited.

Summary

  • Render arrays with map(); filter and sort (with non-mutating methods) first.
  • Give every item a stable, unique key from your data.
  • Index keys mix up state when lists change – the live demo shows why.

Finished this lesson?

Mark it complete to track your progress, then carry on with the next lesson.

Next lesson

Search LearnFrenzy

Esc

Quick links