ReactRendering Lists with map()

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:

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>
Note
Every element returned by `map()` in JSX must have a `key` prop. React uses keys to efficiently update the DOM when the list changes. We'll cover keys in depth on the next page — for now, use a unique string or number from your data.
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:

JSX
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:

JSX
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:

JSX
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:

JSX
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:

JSX
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:

JSX
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

JSX
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>
  )
}
Warning
Never mutate the array directly inside a `map()` callback. `map()` should return new values without side effects. Use `filter()` to remove items, spread syntax (`[...array, newItem]`) to add items, and `map()` with a condition to update items.
  • Use array.map() to transform data arrays into JSX elements

  • Every element in a mapped list needs a unique key prop

  • Use filter() before map() to render only matching items

  • Use slice().sort() or toSorted() to avoid mutating source arrays

  • Handle empty arrays with an early return or conditional rendering

  • Prefer named components over inline JSX for complex list items

Tip
When the list item JSX grows beyond 3-4 lines, extract it to its own component (e.g., `ProductCard`, `UserRow`). This keeps the list component focused on iteration and makes each item independently testable.