Rendering Lists with map()
Displaying a list of items is one of the most fundamental things a UI does — product listings, message threads, task lists, search results. In React, you render lists by transforming an array of data into an array of JSX elements using JavaScript's built-in Array.map() method.
The Basic Pattern
map() iterates over an array and returns a new array where each element has been transformed. Since JSX is just JavaScript, you can use map() directly inside your JSX:
const fruits = ['Apple', 'Banana', 'Cherry', 'Mango']
function FruitList() {
return (
<ul>
{fruits.map(fruit => (
<li key={fruit}>{fruit}</li>
))}
</ul>
)
}
// Renders:
// <ul>
// <li>Apple</li>
// <li>Banana</li>
// <li>Cherry</li>
// <li>Mango</li>
// </ul>Rendering Arrays of Objects
In real apps, lists come from arrays of objects. Map each object to a component or JSX element, using the object's unique ID as the key:
const users = [
{ id: 1, name: 'Alice Chen', role: 'Engineer', avatar: '/alice.jpg' },
{ id: 2, name: 'Bob Okafor', role: 'Designer', avatar: '/bob.jpg' },
{ id: 3, name: 'Carol Ray', role: 'Manager', avatar: '/carol.jpg' },
]
function UserCard({ user }) {
return (
<div className="user-card">
<img src={user.avatar} alt={user.name} />
<h3>{user.name}</h3>
<p>{user.role}</p>
</div>
)
}
function UserList() {
return (
<div className="user-grid">
{users.map(user => (
<UserCard key={user.id} user={user} />
))}
</div>
)
}Filtering Before Mapping
Chain filter() before map() to render only items that match a condition. filter() returns a new array of items for which the callback returns true:
const tasks = [
{ id: 1, title: 'Write tests', done: true },
{ id: 2, title: 'Fix layout bug', done: false },
{ id: 3, title: 'Update docs', done: false },
{ id: 4, title: 'Deploy to staging', done: true },
]
function ActiveTaskList() {
const activeTasks = tasks.filter(task => !task.done)
return (
<ul>
{activeTasks.map(task => (
<li key={task.id}>{task.title}</li>
))}
</ul>
)
}
// Or inline in JSX (same result):
function ActiveTaskListInline() {
return (
<ul>
{tasks
.filter(task => !task.done)
.map(task => (
<li key={task.id}>{task.title}</li>
))
}
</ul>
)
}Sorting Lists
Use slice() before sort() to avoid mutating the original array — sort() sorts in place and can cause subtle bugs if you mutate React state directly:
function SortedProductList({ products }) {
// slice() creates a copy so we don't mutate the original
const sorted = products.slice().sort((a, b) => a.name.localeCompare(b.name))
return (
<ul>
{sorted.map(product => (
<li key={product.id}>
{product.name} — ${product.price}
</li>
))}
</ul>
)
}
// Modern alternative using the non-mutating toSorted() (ES2023)
const sorted = products.toSorted((a, b) => a.name.localeCompare(b.name))Dynamic Lists with State
Lists usually come from state, and state changes cause React to re-render with the updated list. Here's a complete interactive example:
import { useState } from 'react'
let nextId = 1
function TodoApp() {
const [todos, setTodos] = useState([
{ id: nextId++, text: 'Buy groceries', done: false },
{ id: nextId++, text: 'Walk the dog', done: false },
])
const [input, setInput] = useState('')
function addTodo() {
if (!input.trim()) return
setTodos(prev => [...prev, { id: nextId++, text: input, done: false }])
setInput('')
}
function toggleTodo(id) {
setTodos(prev =>
prev.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
)
)
}
function removeTodo(id) {
setTodos(prev => prev.filter(todo => todo.id !== id))
}
return (
<div>
<div>
<input
value={input}
onChange={e => setInput(e.target.value)}
onKeyDown={e => e.key === 'Enter' && addTodo()}
placeholder="New todo..."
/>
<button onClick={addTodo}>Add</button>
</div>
<ul>
{todos.map(todo => (
<li key={todo.id} style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
<input
type="checkbox"
checked={todo.done}
onChange={() => toggleTodo(todo.id)}
/>
{todo.text}
<button onClick={() => removeTodo(todo.id)}>×</button>
</li>
))}
</ul>
<p>{todos.filter(t => t.done).length} of {todos.length} complete</p>
</div>
)
}Nested Lists with flatMap()
For data with a nested structure, flatMap() can transform and flatten in one pass:
const departments = [
{
id: 1,
name: 'Engineering',
members: ['Alice', 'Bob', 'Carol'],
},
{
id: 2,
name: 'Design',
members: ['Diana', 'Edward'],
},
]
// Render all members in a flat list with a department header
function OrgChart() {
return (
<ul>
{departments.flatMap(dept => [
<li key={dept.id} className="dept-header">{dept.name}</li>,
...dept.members.map(member => (
<li key={`${dept.id}-${member}`} className="member">
{member}
</li>
)),
])}
</ul>
)
}Conditional Items Inside a List
You can combine map() with conditional rendering. Returning null from map() is fine — React skips null items:
function SearchResults({ results, searchTerm }) {
if (results.length === 0) {
return <p>No results for "{searchTerm}"</p>
}
return (
<ul>
{results.map(result => (
<li key={result.id}>
<a href={result.url}>{result.title}</a>
{result.isPremium && <span className="tag">Premium</span>}
<p>{result.snippet}</p>
</li>
))}
</ul>
)
}Empty State Handling
function ProductGrid({ products }) {
if (products.length === 0) {
return (
<div className="empty-state">
<p>No products found.</p>
<a href="/shop">Browse all products</a>
</div>
)
}
return (
<div className="grid">
{products.map(p => (
<ProductCard key={p.id} product={p} />
))}
</div>
)
}Use
array.map()to transform data arrays into JSX elementsEvery element in a mapped list needs a unique
keypropUse
filter()beforemap()to render only matching itemsUse
slice().sort()ortoSorted()to avoid mutating source arraysHandle empty arrays with an early return or conditional rendering
Prefer named components over inline JSX for complex list items