- Home
- /
- Tutorials
- /
- React Tutorial
- /
- Rendering Lists
Lists and Keys
Rendering Lists
Rendering a list is
map. There is no loop syntax in JSX because an array of elements is already something React knows how to render.
map returns elements
Because a JSX element is just a value, mapping an array of data to an array of elements is ordinary JavaScript. React renders each item in order.
Data to elements
jsx
function List() {
const people = [
{ id: 1, name: "Ada", role: "Engineer" },
{ id: 2, name: "Grace", role: "Admiral" },
{ id: 3, name: "Alan", role: "Logician" },
]
return (
<ul>
{people.map((person) => (
<li key={person.id}>
<strong>{person.name}</strong> — {person.role}
</li>
))}
</ul>
)
}
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(<List />)Note the arrow returns the element directly, with parentheses rather than braces. Writing { opens a function body, and then you must return - forgetting to is a common way to render nothing.
Filtering and sorting
Both happen before the map, as ordinary array work. The one trap is sort, which mutates - always sort a copy, or you are mutating state.
Filter, then sort a copy
jsx
function Roster() {
const [onlyActive, setOnlyActive] = React.useState(false)
const people = [
{ id: 1, name: "Ada", active: true },
{ id: 2, name: "Grace", active: false },
{ id: 3, name: "Alan", active: true },
]
const shown = people
.filter((person) => (onlyActive ? person.active : true))
.slice() // copy before sorting
.sort((a, b) => a.name.localeCompare(b.name))
return (
<div>
<button onClick={() => setOnlyActive((v) => !v)}>
{onlyActive ? "Show everyone" : "Only active"}
</button>
<ul>
{shown.map((person) => <li key={person.id}>{person.name}</li>)}
</ul>
</div>
)
}
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(<Roster />)The empty case
An empty array renders nothing at all - not an error, just silence. That is rarely what you want a user to see, so handle it explicitly.
Say something when there is nothing
jsx
function Items({ items }) {
if (items.length === 0) {
return <p style={{ color: "#64748b" }}>Nothing here yet.</p>
}
return <ul>{items.map((item) => <li key={item}>{item}</li>)}</ul>
}
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(
<div>
<Items items={["one", "two"]} />
<Items items={[]} />
</div>
)Resist {items.length && ...} here - that is the stray-zero bug from the previous section. An early return is clearer and cannot misfire.
Nested lists
A list inside a list is two maps, and each needs its own key. Keys only have to be unique among their immediate siblings, so an inner list can reuse the same values as another inner list without any conflict.
A map inside a map
jsx
function Groups() {
const groups = [
{ id: "a", title: "Fruit", items: ["apple", "pear"] },
{ id: "b", title: "Tools", items: ["hammer", "saw"] },
]
return (
<div>
{groups.map((group) => (
<div key={group.id}>
<strong>{group.title}</strong>
<ul>
{group.items.map((item) => <li key={item}>{item}</li>)}
</ul>
</div>
))}
</div>
)
}
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(<Groups />)mapdata to elements; there is no loop syntax.- Use parentheses in the arrow, or remember to
return. sortmutates - copy first.- Handle the empty array deliberately.
- Nested lists need a key at each level; siblings are the only scope.
