Components and Props · Lesson 7 of 31
Rendering Lists and Keys in React
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>
);
}
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>
</>
);
}
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>
);
}
• 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} />
</>
);
}
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>
</>
);
}
Progress: 2/3 • ✔ JSX in React • ✔ React Props • ○ Lists and Keys
Practice
Try it yourself
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> ))} </> ); }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.