List rendering
Turn an array into elements with map, filtering first and giving every item a stable key.
{items.filter(fn).map((item) => <li key={item.id}>{item.name}</li>)}Parameters
| Parameter | Type | Description |
|---|---|---|
$items | array | The source data. Never mutate it while rendering — sort and filter on a copy, since sort() mutates in place. |
$callback | (item, index) => ReactNode | Called for each item; must return an element carrying a key that is unique among its siblings. |
Return value
ReactNode[]An array of React nodes. React renders an array by rendering each element in order; nested arrays are flattened.
Errors
Rendering an array of raw objects throws "Objects are not valid as a React child" — map them to elements or strings first.
There is no loop syntax in JSX. You render a list by producing an array of elements with map() and embedding it in braces — React renders arrays element by element.
The shape of it
Filter, sort and slice before mapping, so the map callback does one job. filter and slice return new arrays, but sort and reverse mutate, so copy first: [...items].sort(...). Mutating props or state during render is a bug even when it appears to work.
Keys
Every element created directly inside map() needs a key that is stable and unique among its siblings — an id from your data, not the array index and never a value generated during render.
What trips people up
Using forEach instead of map renders nothing, because forEach returns undefined. So does an arrow function with a body block that forgets to return — {items.map(i => { <li/> })} silently produces a list of undefined.
Handle the empty case explicitly. An empty array renders nothing at all, which looks identical to a bug; a short "no results" branch is worth the two lines.
Index is still fine as the second callback argument for display purposes, such as row numbering — just not as the key.
Example
When to use it
- Rendering search results filtered by a query string
- Showing a table of rows fetched from an API
- Building nav links from a configuration array
- Grouping items by category and rendering a section per group
Frequently asked questions
How do I loop inside JSX?
Use array.map() and embed the result in braces; JSX has no for or while syntax because the braces take an expression. If you already have a loop, build the array above the return and render that variable.
Why is my list rendering nothing?
Usually because the callback returns undefined — either forEach was used instead of map, or an arrow function with a { } body has no return statement. An empty source array also renders nothing, so log its length before blaming React.
Why do I get 'Objects are not valid as a React child'?
Because an object was placed directly in the output, for example {user} rather than {user.name}, or an array of raw data was embedded without mapping it to elements. Render a specific string property, or map each object to JSX.
More examples
Grouping into sections
Two nested maps, each with its own key namespace: the section keys are column names, the item keys are task ids, and there is no requirement that they be unique with respect to each other. The outer callback uses a block body, so it needs an explicit return — the single most common cause of a grouped list rendering nothing. The "done" column shows the empty branch.
const TASKS = [
{ id: "t1", title: "Write docs", status: "todo" },
{ id: "t2", title: "Fix flake", status: "doing" },
{ id: "t3", title: "Ship 1.4", status: "todo" }
];
const COLUMNS = ["todo", "doing", "done"];
export default function Board() {
return (
<div>
{COLUMNS.map((status) => {
const tasks = TASKS.filter((t) => t.status === status);
return (
<section key={status}>
<h4>
{status} ({tasks.length})
</h4>
{tasks.length === 0 ? (
<p>Empty</p>
) : (
<ul>
{tasks.map((t) => (
<li key={t.id}>{t.title}</li>
))}
</ul>
)}
</section>
);
})}
</div>
);
}Extracting the row component
Once a row grows past a couple of elements, pull it into its own component. The key stays on the <Row /> in the map, not on the <tr> inside Row — the key belongs wherever the array element is created. Row itself cannot read it: if the component needs the id it must receive it as a normal prop, which it does here via user.
function Row({ user, onSelect }) {
return (
<tr onClick={() => onSelect(user.id)}>
<td>{user.name}</td>
<td>{user.email}</td>
</tr>
);
}
const USERS = [
{ id: 7, name: "Ada", email: "[email protected]" },
{ id: 9, name: "Grace", email: "[email protected]" }
];
export default function UserTable() {
return (
<table>
<tbody>
{USERS.map((u) => (
<Row key={u.id} user={u} onSelect={(id) => console.log("selected", id)} />
))}
</tbody>
</table>
);
}
Discussion