React course menu
Rendering Lists in React with map() and Keys
Display arrays in React: turning data into elements with map(), why every list item needs a stable key, choosing keys, filtering and sorting before rendering, rendering tables, and the problem with array indexes as keys.
On this page
Most interfaces show lists: products, messages, search results, table rows. In React you keep the data in an array and turn each item into JSX with the array's map() method.
map() turns data into elements#
const fruits = ["Mango", "Banana", "Guava"];
export default function App() {
return (
<ul>
{fruits.map((fruit) => (
<li key={fruit}>{fruit}</li>
))}
</ul>
);
}map returns a new array of <li> elements, and React renders an array of elements one after another.
Keys#
Each item in a list needs a key prop: a value that is unique among its siblings and stays the same for that item. Keys let React match items between renders, so when the list changes (an item is added, removed or reordered) it updates the right elements and keeps each item's state with the right item. Without keys, React warns in the console: Each child in a list should have a unique "key" prop.
Real data usually has an id, which makes the best key:
const students = [
{ id: 101, name: "Aarav", marks: 88 },
{ id: 102, name: "Diya", marks: 94 },
{ id: 103, name: "Kabir", marks: 71 },
];
export default function App() {
return (
<table style={{ borderCollapse: "collapse" }}>
<thead>
<tr><th align="left">Roll no.</th><th align="left">Name</th><th align="right">Marks</th></tr>
</thead>
<tbody>
{students.map((s) => (
<tr key={s.id}>
<td>{s.id}</td>
<td style={{ padding: "0 16px" }}>{s.name}</td>
<td align="right">{s.marks}</td>
</tr>
))}
</tbody>
</table>
);
}Rules for keys:
- Use ids from your data (database ids, product codes). For data you create, generate an id when the item is created (for example with
crypto.randomUUID()). - Do not generate keys during rendering (
key={Math.random()}): a new key every render makes React recreate every item. - The key goes on the outermost element returned from
map, and is not passed to the component as a prop.
Why the index is a risky key#
map((item, index) => <li key={index}>) silences the warning, but if items are inserted, removed or reordered, the index of an item changes, and React attaches the wrong state to the wrong row. Try it: type something in the boxes, then add an item at the top.
import { useState } from "react";
export default function App() {
const [tasks, setTasks] = useState([
{ id: 1, text: "Buy milk" },
{ id: 2, text: "Call plumber" },
]);
const addToTop = () => setTasks([{ id: Date.now(), text: "New task" }, ...tasks]);
return (
<div style={{ display: "flex", flexWrap: "wrap", gap: 24 }}>
<div>
<strong>key = index</strong>
{tasks.map((task, index) => (
<p key={index}>{task.text} <input placeholder="note" size={8} /></p>
))}
</div>
<div>
<strong>key = id</strong>
{tasks.map((task) => (
<p key={task.id}>{task.text} <input placeholder="note" size={8} /></p>
))}
</div>
<button onClick={addToTop} style={{ alignSelf: "start" }}>Add task at top</button>
</div>
);
}With index keys, the notes you typed stay in the first rows while the task names shift down, so they end up next to the wrong tasks. With id keys, each note moves with its task. The index is only safe for lists that never change order and never have items inserted or removed.
Filter and sort before rendering#
Prepare the array first with filter, sort or slice, then map it:
const products = [
{ id: "p1", name: "Headphones", price: 1999, inStock: true },
{ id: "p2", name: "Keyboard", price: 1299, inStock: false },
{ id: "p3", name: "Mouse", price: 599, inStock: true },
{ id: "p4", name: "Webcam", price: 2499, inStock: true },
];
export default function App() {
const available = products
.filter((p) => p.inStock)
.toSorted((a, b) => a.price - b.price);
return (
<div>
<p>{available.length} of {products.length} products in stock, cheapest first:</p>
<ol>
{available.map((p) => (
<li key={p.id}>{p.name} — ₹{p.price}</li>
))}
</ol>
</div>
);
}toSorted returns a new sorted array. The older sort sorts the array in place, which would change the original data during rendering; avoid that (or copy first with [...products].sort(...)).
Rendering components from a list#
function Contact({ name, phone }) {
return (
<div style={{ borderBottom: "1px solid #eee", padding: "6px 0" }}>
<strong>{name}</strong><br />
<small>{phone}</small>
</div>
);
}
const contacts = [
{ id: "c1", name: "Mom", phone: "98450 12345" },
{ id: "c2", name: "Office", phone: "080 4123 4567" },
];
export default function App() {
return (
<div>
{contacts.map((c) => (
<Contact key={c.id} name={c.name} phone={c.phone} />
))}
</div>
);
}Empty lists#
Always handle the empty case, so users see a message rather than a blank space:
{items.length === 0 ? <p>No results found.</p> : <ul>{items.map(...)}</ul>}Common mistakes#
Practice#
Practice exercise
Given the movies below, show only those with a rating of 8 or more, highest rating first, as a list "Title (year) ⭐ rating". Show "No movies" if none match.
const movies = [
{ id: 1, title: "Lagaan", year: 2001, rating: 8.1 },
{ id: 2, title: "Dhoom", year: 2004, rating: 6.7 },
{ id: 3, title: "3 Idiots", year: 2009, rating: 8.4 },
{ id: 4, title: "Taare Zameen Par", year: 2007, rating: 8.3 },
];Show hint
movies.filter((m) => m.rating >= 8).toSorted((a, b) => b.rating - a.rating), then map to <li key={m.id}>.
Show solution
const movies = [
{ id: 1, title: "Lagaan", year: 2001, rating: 8.1 },
{ id: 2, title: "Dhoom", year: 2004, rating: 6.7 },
{ id: 3, title: "3 Idiots", year: 2009, rating: 8.4 },
{ id: 4, title: "Taare Zameen Par", year: 2007, rating: 8.3 },
];
export default function App() {
const top = movies.filter((m) => m.rating >= 8).toSorted((a, b) => b.rating - a.rating);
if (top.length === 0) return <p>No movies</p>;
return (
<ul>
{top.map((m) => (
<li key={m.id}>{m.title} ({m.year}) ⭐ {m.rating}</li>
))}
</ul>
);
}Key takeaway#
Next: Handling Events.
Finished this lesson?