Lists and Keys
Turning arrays into elements with map, and why every item needs a stable key.
Rendering an array
React renders arrays of elements directly, so map is all you need.
function Languages() {
const items = ["JavaScript", "TypeScript", "Rust"];
return (
<ul>
{items.map(item => (
<li key={item}>{item}</li>
))}
</ul>
);
}Use map rather than forEach: you need the returned array, and forEach returns nothing.
Lists of objects
const posts = [
{ id: 1, title: "Getting started", author: "Ada" },
{ id: 2, title: "Thinking in components", author: "Grace" },
];
function PostList() {
return (
<div>
{posts.map(post => (
<article key={post.id}>
<h3>{post.title}</h3>
<p>by {post.author}</p>
</article>
))}
</div>
);
}The key goes on the outermost element returned by the callback — not on a child inside it.
What keys are for
When a list changes, React needs to know which items are the same ones as before. Keys give each item an identity, so React can move an existing element rather than rebuilding it.
Without stable keys, React falls back to matching by position. If you insert an item at the front, every element after it looks changed, and any state held inside those elements — the text typed into an input, a checkbox tick, scroll position — ends up attached to the wrong row.
Choosing a key
- A database id is the best key.
- A value guaranteed unique within the list, such as a slug or an email address, also works.
- Generating a key at render time with Math.random() or a new uuid is wrong — it changes every render, so React rebuilds the whole list each time.
Keys must be unique among siblings, not globally. Two different lists on the same page may both use key 1.
The index as a key
{items.map((item, index) => (
<li key={index}>{item}</li>
))}This is safe only when all three of these hold: the list never reorders, items are never inserted or removed except at the end, and the items have no state or form inputs. If any of those may change, use a real id.
Extracting a component
When a row grows beyond a few lines, move it into its own component. The key stays on the element in the list, not inside the component definition.
function Post({ post }) {
return (
<article>
<h3>{post.title}</h3>
<p>by {post.author}</p>
</article>
);
}
function PostList({ posts }) {
return (
<div>
{posts.map(post => (
<Post key={post.id} post={post} />
))}
</div>
);
}key is consumed by React and does not arrive as a prop. If a component also needs the value, pass it a second time under another name, such as id={post.id}.
Filtering, sorting, and empty lists
function Results({ items, query }) {
const matches = items
.filter(item => item.title.includes(query))
.sort((a, b) => a.title.localeCompare(b.title));
if (matches.length === 0) {
return <p>No results for “{query}”. Try a shorter search.</p>;
}
return (
<ul>
{matches.map(item => (
<li key={item.id}>{item.title}</li>
))}
</ul>
);
}Note that filter and sort run here on a copy produced by filter, so the original array is not mutated. Sorting a prop or state array directly would be a bug.
Fragments in a list
When each item renders several sibling elements with no wrapper, use the long form of the fragment so it can carry a key.
{entries.map(entry => (
<React.Fragment key={entry.id}>
<dt>{entry.term}</dt>
<dd>{entry.definition}</dd>
</React.Fragment>
))}