Lists & Keys
Learn how to turn any JavaScript array into a list of React elements using .map(), and discover why the key prop is the secret ingredient that keeps things fast and correct.
Almost every real-world app displays collections of things — a todo list, a set of product cards, a stream of messages, a nav menu. React doesn't have a special "list" syntax for this. Instead, it leans on something you already know from plain JavaScript: arrays. The moment you realize that JSX is just JavaScript, turning a list of data into a list of UI becomes natural. This lesson shows you exactly how, and introduces the key prop — a small detail that prevents a surprising class of bugs.
#From Data to UI: The .map() Pattern
In React, if you want to render a list, you convert an array of data into an array of JSX elements. The tool for that is the native JavaScript Array.prototype.map() method. It takes every item in an array and transforms it into something else — in our case, into a JSX element.
Here's a simple array of fruit names rendered as list items:
const fruits = ['Apple', 'Banana', 'Cherry'];
function FruitList() {
return (
<ul>
{fruits.map((fruit) => (
<li key={fruit}>{fruit}</li>
))}
</ul>
);
}Notice the pattern: you write {array.map((item) => <Element .../>)} right inside your JSX. React sees the resulting array of elements and renders each one in order. The curly braces are the door between JSX and JavaScript — and .map() lives on the JavaScript side.
A Stamping Machine
.map() is like a stamping machine on a factory floor. You feed raw blanks (your data array) in one end, and out the other end comes one stamped part (a JSX element) for every blank that went in. The machine itself doesn't care how many blanks you give it — ten or ten thousand, each one gets the same treatment.
#Working with Objects — Richer Data
In real apps your data will almost always be an array of objects, not plain strings. Each object has an id and other fields. Here's how you'd render a list of user cards:
const users = [
{ id: 1, name: 'Aiko Tanaka', role: 'Designer' },
{ id: 2, name: 'Marcus Bell', role: 'Engineer' },
{ id: 3, name: 'Sofia Reyes', role: 'PM' },
];
function UserList() {
return (
<ul>
{users.map((user) => (
<li key={user.id}>
<strong>{user.name}</strong> — {user.role}
</li>
))}
</ul>
);
}#What Is the key Prop, and Why Does React Need It?
You may have noticed key={user.id} in every example. If you leave it off, React will still render the list — but it will print a warning in the console. The key prop is not something you use inside the component; it's a hint to React itself about how to track each element across re-renders.
Here's the problem React is solving: when your list changes — an item is added, removed, or reordered — React needs to figure out which existing DOM nodes to update, reuse, or remove. Without a key, React's only option is to compare elements by their position. With a key, it can match elements by identity, even if they've moved.
Nameplates on Seats
Imagine a classroom where students can move around between lessons. If you only track seats by row number, and a student in row 2 leaves, everyone behind them shifts up — and suddenly your notes say row 3 is "Maria" when it's now "Carlos". But if every seat has a nameplate, you can always find the right student regardless of where they're sitting. The key is React's nameplate — a stable identity that survives reordering.
#Why Index-as-Key Can Cause Bugs
When developers first see the key warning, a common instinct is to silence it with the array index — the second argument .map() provides automatically:
// Tempting, but dangerous when the list can change
{items.map((item, index) => (
<li key={index}>{item.name}</li>
))}This works fine for a static list that never changes. The moment your list can be reordered, filtered, or have items inserted or removed in the middle, you have a problem: the index of each item changes, so React thinks the element at position 0 is still the same element, even if it's now showing completely different data. This can cause UI state — like text in an <input> inside a list item — to get attached to the wrong item.
Index Keys + Stateful Children = Silent Corruption
Consider a list of <input> fields keyed by index. The user types into item 2. Then item 1 is deleted. React reuses the same DOM node for what is now item 2 (was item 3) — but the key is still 1, so React thinks nothing moved. The typed value stays in the wrong input. This bug is invisible until it happens in production with real users. Rule of thumb: only use index keys when the list is static and never reordered.
#The Right Key: Stable, Unique, Not Random
A good key has three properties:
- Unique among siblings — two items in the same list can't share a key (though items in different lists can).
- Stable across re-renders — the same item must get the same key every time the component re-renders.
- Not randomly generated — writing
key={Math.random()}gives a new key every render, defeating the entire purpose.
If your data comes from a database, the row's primary key (e.g. user.id) is almost always the right choice. If you generate data on the client, use a library like uuid or an incrementing counter to assign IDs when the item is created, not when it is rendered.
// Good: stable database ID
<li key={post.id}>{post.title}</li>
// Good: stable client-generated ID (assigned at creation)
<li key={task.uuid}>{task.label}</li>
// Bad: changes every render
<li key={Math.random()}>{item.name}</li>
// Bad: changes when list is reordered
<li key={index}>{item.name}</li>#Extracting List Items Into a Component
As your list items grow more complex, it's a good idea to extract each item into its own component. One important rule: the `key` prop goes on the outermost element returned by `.map()` — not inside the child component.
function UserCard({ user }) {
// key is NOT a prop — you can't read it inside here
return (
<li>
<strong>{user.name}</strong> — {user.role}
</li>
);
}
function UserList({ users }) {
return (
<ul>
{users.map((user) => (
<UserCard key={user.id} user={user} />
))}
</ul>
);
}key Is Not a Prop
Even though you write key={...} like a prop, React intercepts it and never passes it through. If you try to read props.key inside UserCard, you'll get undefined. If your child component genuinely needs the ID for something (like fetching data), pass it as a separate prop: <UserCard key={user.id} id={user.id} user={user} />.
A developer has a todo list that can be reordered by the user. Which key choice is correct?
Key takeaways
- Use array.map() to transform data arrays into arrays of JSX elements — it's plain JavaScript inside curly braces.
- The key prop is React's way of tracking which list item is which across re-renders; always provide one.
- Keys must be stable and unique among siblings — a database ID or a client-generated UUID is ideal.
- Using the array index as a key is safe only for static lists that are never reordered, filtered, or have items inserted in the middle.
- The key prop belongs on the outermost element in the .map() call; it is never accessible as a prop inside the component.
This code has a bug — what's wrong?
const users = [
{ id: 1, name: 'Aiko Tanaka' },
{ id: 2, name: 'Marcus Bell' },
];
function UserList() {
return (
<ul>
{users.map((user) => (
<li>{user.name}</li>
))}
</ul>
);
}Complete the .map() so each user card gets a stable, unique key. Use the object field the lesson recommends — not the display name and not the array index.
<ul> {users.map((user) => ( <li key={user.}>{user.name}</li> ))} </ul>
The UserCard child logs props.key when it renders. Given the code below, what does the console print for the first item?
function UserCard({ user }) {
console.log(props.key);
return <li>{user.name}</li>;
}
function UserList({ users }) {
return users.map((user) => (
<UserCard key={user.id} user={user} />
));
}Arrange these lines into a correct TodoList component that renders a reorderable todo list using a stable key from each item's id.
);
</ul>
function TodoList({ todos }) {))}
<li key={todo.id}>{todo.text}</li> {todos.map((todo) => (<ul>
return (
}
You have an array of books objects, each with an id, title, and author. Render them as an unordered list where each item shows the title in bold followed by the author. Make sure to use the correct key. Then add a button that removes the first book from the list — verify that the remaining items display correctly (this is where a bad key would cause trouble).
Try it live — edit the code and hit Run to see it rendered: