Command Palette

Search for a command to run...

Hectal
PHASE 3Beginner ~29 min· topic 2 of 4

Topic 3.2

Rendering Lists with map

In one line

To show a list, keep the data in an array and turn every item into an element with map; use filter first to choose which items appear, and move the look of one item into its own component.

Think of it like this

A cookie cutter. You don't carve every cookie by hand. You make one cutter and press it into the dough once per cookie. In React, the cutter is a small piece of JSX (or a component) and the dough is your array of data: map presses the cutter once for every item and gives you a tray of cookies, an array of elements.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Array
An ordered list of values, written in square brackets: ["a", "b", "c"].
map
An array method that calls a function on every item and returns a new array of the results.
filter
An array method that returns a new array with only the items that pass a test.
Array of objects
A list where every item is an object with fields, like [{ id: 1, name: "Asha" }, ...].
Derived data
Values you can calculate from other data (like the filtered list), so you compute them during render instead of storing them.
List item component
A small component that draws one item of a list, used inside map.
forEach
An array method that runs a function for every item but returns nothing (undefined). Not useful inside JSX.

Step by step

01Keep the data in an array

Start with the data, not the markup. Each object is one book; the id is a unique label for it.

books.jswhole filejavascript
const books = [
  { id: "b1", title: "Malgudi Days", pages: 256 },
  { id: "b2", title: "The God of Small Things", pages: 340 },
  { id: "b3", title: "Wings of Fire", pages: 196 },
];

02Describe one item, map over all of them

Write what one book looks like, and let map repeat it. The result is an array of three <li> elements, which JSX renders in order.

BookList.jsxwhole filejsx
function BookList() {
  return (
    <ul>
      {books.map((book) => (
        <li key={book.id}>{book.title} ({book.pages} pages)</li>
      ))}
    </ul>
  );
}

03Filter first to choose which items show

Chain filter before map. Nothing is stored: the shorter list is worked out from the full list on every render.

BookList.jsxwhole filejsx
const short = books.filter((b) => b.pages < 300);
return <ul>{short.map((b) => <li key={b.id}>{b.title}</li>)}</ul>;
Filter first to choose which items showdiagram
Rendering diagram…

04Move one item into its own component

When an item has more than a line of JSX, give it a component. The list passes each item as a prop and puts the key on the component element.

BookList.jsxwhole filejsx
function BookRow({ book }) {
  return (
    <li>
      <strong>{book.title}</strong> <small>{book.pages} pages</small>
    </li>
  );
}

function BookList({ books }) {
  return <ul>{books.map((b) => <BookRow key={b.id} book={b} />)}</ul>;
}

05The list follows the data

Add an object to the array (or let the user add one through state) and the next render maps over four items instead of three. You never write "insert a new <li>". This is UI = f(state) from Topic 0.1 applied to lists.

Try it yourself

  1. 1

    Add a book

    In the first example, add a fourth book object with id: "b4". Predict the console line before running. (map made 4 elements.)

  2. 2

    Add a sort

    In the fruit shop, add .toSorted((a, b) => a.price - b.price) after the two filters. The list now goes from cheapest to dearest, and fruits itself is untouched because toSorted returns a copy.

  3. 3

    Show an empty message

    Type kiwi in the fruit search: the list is empty and the page looks broken. Add {visible.length === 0 && <p>No fruit matches.</p>} under the count. (Topic 3.4 covers empty states properly.)

  4. 4

    Destructure in the parameter

    In the team list, change PersonCard({ person }) to PersonCard({ person: { name, role, online } }) and update the JSX to use name, role and online. Same page, shorter JSX.

Code & diagrams

From an array of objects to a list New tab

`items` is an ordinary array of React elements. Putting an array inside `<ul>{...}</ul>` renders every element in order.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

map made 3 elements
Filter, then map: a searchable list New tab

Type `an` or tick **Under ₹100**. The visible list is calculated from the full list and the two inputs on every render; it is never stored.

Preview

Press Run to see the page.

Console

Nothing logged yet.
Extract a list item component New tab

`TeamList` decides which people and in what order; `PersonCard` decides how one person looks. Change the card once and every row changes.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Use forEach instead of map

In the first example, change books.map( to books.forEach(.

terminal
$ Console and preview
── what you'll see ──
Uncaught TypeError: Cannot read properties of undefined (reading 'length')
An error occurred in the <BookList> component.
 
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://react.dev/link/error-boundaries to learn more about error boundaries.

Break #2

Render a whole object

In the team list, replace <strong>{person.name}</strong> with <strong>{person}</strong>.

terminal
$ Console
── what you'll see ──
Uncaught Error: Objects are not valid as a React child (found: object with keys {id, name, role, online}). If you meant to render a collection of children, use an array instead.
An error occurred in the <strong> component.
 
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://react.dev/link/error-boundaries to learn more about error boundaries.

Myth vs fact

Myth

React has a special loop syntax for lists.

Fact

There is no v-for or *ngFor. You use JavaScript's map, which returns an array, and JSX renders arrays.

Myth

You should store the filtered list in state.

Fact

A filtered list can always be calculated from the full list and the filter values, so compute it during render. Storing it as well means two copies that can disagree.

Myth

A for loop can't be used in React.

Fact

It can, outside the JSX: push elements into an array in a for loop, then render the array. map is preferred because it is an expression and fits inside JSX.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    JSX children can be arrays, and arrays can be nested: <ul>{[a, [b, c]]}</ul> renders all three. React flattens nested arrays when rendering, but each array is its own keyed group for reconciliation.

  • ▸

    filter + map in render is O(n) on every render. For a few hundred items it is negligible; when profiling shows a slow calculation, cache it with useMemo (Topic 16.4) or let the React Compiler do it (Topic 16.5). Don't move it into state plus an effect: that adds an extra render and a sync bug.

  • ▸

    toSorted, toReversed and toSpliced (ES2023) return copies, which makes sorting inside render safe. Calling items.sort() on a prop or state array mutates it in place: a bug that is easy to miss because the screen still looks right (Topic 4.7).

  • ▸

    Extracting an item component also gives React a natural place to skip work later: with memo, unchanged rows can skip rendering when one row changes (Topic 16.3).

Remember this

  1. 1

    In real apps lists come from data: products from a server, messages, search results. You don't write <li> ten times by hand. You keep the items in an array (an ordered list of values) and describe how *one* item looks. When the array changes (a product is added, a message arrives) the list on screen follows automatically, because it is computed from the array on every render.

  2. 2

    **map turns data into elements.** array.map(fn) calls fn once for every item and returns a new array of whatever fn returned. In JSX that is an array of elements: books.map((book) => <li key={book.id}>{book.title}</li>). JSX can render arrays directly, so <ul>{items}</ul> shows every element in it, in order. map doesn't change the original array, which matters because React data should never be changed in place (Topic 4.7).

  3. 3

    Arrays of objects are the usual shape of list data: { id: "b1", title: "Malgudi Days", pages: 256 }. Inside the arrow function you read the fields you need (book.title), or destructure them: books.map(({ id, title }) => <li key={id}>{title}</li>). Each item may show several fields, a conditional class, nested elements, anything a normal piece of JSX can do. The key you see on each item is explained fully in Topic 3.3; for now, always give it the item's id.

  4. 4

    **filter then map.** To show only some items, chain filter before map: fruits.filter((f) => f.price < 100).map((f) => <li key={f.id}>{f.name}</li>). filter(fn) returns a new array with only the items for which fn returns true. Keep derived data like this as a plain calculation during render, not in its own state: compute visible from fruits and query each render, so it can never fall out of step (Topic 6.2). You can also sort (copy first: [...items].sort(...) or items.toSorted(...)) or slice before mapping.

  5. 5

    Watch the arrow function's body. (x) => <li>{x}</li> returns the element. (x) => { <li>{x}</li> } with curly braces is a block with no return, so every item becomes undefined and the list is empty with no error. And use map, not forEach: forEach always returns undefined, so {items.forEach(...)} renders nothing.

  6. 6

    Extract a list item component. When one item's JSX grows (an avatar, a name, a role, a status dot), move it into its own component: function PersonCard({ person }) { ... }, then people.map((p) => <PersonCard key={p.id} person={p} />). The list component handles *which* items and *in what order*; the item component handles *how one looks*. The key stays in the map, on the <PersonCard> element, not inside PersonCard. Very long lists (thousands of rows) need extra techniques such as virtualisation (Topic 16.9).

Explain it without notes

01

How do you render a list in React? Why map and not forEach or a for loop inside JSX?

02

Why should a filtered list be calculated during render rather than stored in state?

03

When should you extract a list item into its own component, and where does the key go then?

04

A list renders nothing and the console is empty. What are the usual causes?

Practice

01

Given const cities = ["Delhi", "Mumbai", "Chennai"], render them as an ordered list (<ol>).

02

Given an array of { id, name, inStock } products, render only the ones in stock, each as <li> with the name.

03

Extract a ScoreRow component that shows a player's name and score, and a ScoreBoard that renders an array of { id, name, score } with it.

Trade-offs

  • ↔

    Computing filtered lists in render keeps one source of truth at the cost of repeating the work every render; that cost only matters for big lists or slow filters, where useMemo fixes it.

  • ↔

    Extracting an item component adds a file or a function, but keeps the list readable and lets each row be styled, tested and optimised on its own.

Done when you can

  • I can render an array of objects with map.

  • I can filter (and sort a copy) before mapping.

  • I calculate filtered lists during render instead of storing them.

  • I can extract a list item component and put the key on it in the map.

  • I can spot the missing return and forEach mistakes.