Keys and Lists
Lists are one of the first places where React's rendering model becomes visible.
You usually render lists with .map() and give each item a stable key.
function ProjectList({ projects }) {
return (
<ul>
{projects.map((project) => (
<li key={project.id}>{project.name}</li>
))}
</ul>
);
}Rendering Arrays
JSX can render arrays of elements.
const items = [
<li key="html">HTML</li>,
<li key="css">CSS</li>,
<li key="js">JavaScript</li>,
];
function SkillList() {
return <ul>{items}</ul>;
}In real components, arrays usually come from data.
function SkillList({ skills }) {
return (
<ul>
{skills.map((skill) => (
<li key={skill}>{skill}</li>
))}
</ul>
);
}What Makes a Good Key
A good key is:
- unique among siblings
- stable between renders
- tied to the item's identity
Database ids, slugs, and stable generated ids from your data are good choices.
<InvoiceRow key={invoice.id} invoice={invoice} />Values that can change are poor keys.
// Bad if displayName can change
<UserRow key={user.displayName} user={user} />Why Index Keys Can Break UI
Array indexes are tempting.
{todos.map((todo, index) => (
<TodoItem key={index} todo={todo} />
))}This is safe only for static lists that never reorder, insert, or delete items.
Consider a todo list where each row has an input. If you use indexes and insert a new todo at the top, React may preserve the input state by position instead of by todo identity. A draft typed into one row can appear attached to another row.
Use ids instead:
{todos.map((todo) => (
<TodoItem key={todo.id} todo={todo} />
))}Filtering and Sorting
Keys are especially important when the same list can be filtered or sorted.
function ProductList({ products, query }) {
const visibleProducts = products.filter((product) =>
product.name.toLowerCase().includes(query.toLowerCase())
);
return (
<ul>
{visibleProducts.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}Filtering changes positions. Stable keys let React keep track of which items remain.
Empty States
Lists should handle the empty case clearly.
function NotificationList({ notifications }) {
if (notifications.length === 0) {
return <p>No notifications.</p>;
}
return (
<ul>
{notifications.map((notification) => (
<li key={notification.id}>{notification.message}</li>
))}
</ul>
);
}Do not leave the user staring at a blank area when no data exists.
Nested Lists
Keys only need to be unique among siblings, not globally across the app.
function Menu({ sections }) {
return (
<nav>
{sections.map((section) => (
<section key={section.id}>
<h2>{section.title}</h2>
<ul>
{section.links.map((link) => (
<li key={link.href}>
<a href={link.href}>{link.label}</a>
</li>
))}
</ul>
</section>
))}
</nav>
);
}The section keys are compared with other sections. The link keys are compared with other links in the same ul.
Common Mistakes
- Using indexes as keys for lists that can change order.
- Using
Math.random()as a key. This changes every render and forces React to recreate items. - Forgetting keys on the outermost element returned by
.map(). - Putting the key on a child inside the mapped component instead of on the element returned by the map.
- Assuming keys are available as
props.key.
Why is key={Math.random()} a bad idea?
Practice Challenge
Build a ShoppingList component.
Requirements:
- Accept an
itemsarray with{ id, name, quantity }. - Render an empty state if the array is empty.
- Render a list of item names and quantities.
- Use each item's
idas the key. - Add a note explaining why index keys would be risky if items can be removed.
Recap
Lists are rendered with arrays of elements. Stable keys help React preserve identity correctly when items are inserted, removed, filtered, or reordered.