Course outline · 0% complete

0/28 lessons0%

Course overview →

Rendering lists with map

lesson 5-1 · ~10 min · 13/28

What map returns

Lesson 4-3 used tasks.map(...) to change one task inside an array in state. map always returns a new array of the same length, built from the callback's return values.

map, from Advanced JavaScript, transforms each element through a callback and collects the results into a new array, leaving the original untouched. That immutability is what made it right for state updates in 4-3, and it is also how React renders lists.

Both halves of the definition matter here. Same length means one element out per element in, so a list of five tasks becomes five li elements, and returning a new array means the source data is never disturbed by rendering it.

Rendering lists with map

Real apps render arrays: tasks, messages, products. JSX has no loop syntax, and it does not need one. Curly braces accept an array of elements, and map produces exactly that:

function TaskList({ tasks }) {
  return (
    <ul>
      {tasks.map(task => (
        <li key={task.id}>{task.title}</li>
      ))}
    </ul>
  );
}

For tasks = [{id: 1, title: "Water plants"}, {id: 2, title: "Pay rent"}], the map produces an array of two <li> elements and React renders them in order. The key attribute is required on list items, next lesson explains why.

Data in, elements out. Since it is plain JavaScript, you can chain anything before the map: filter to hide items, slice to cap the count, sort (on a copy) to order them.

The same list logic without JSX

Mapping tasks to li strings and joining them, with a ternary adding a check mark to the done task.

const tasks = [
  { id: 1, title: "Water plants", done: true },
  { id: 2, title: "Pay rent", done: false },
  { id: 3, title: "Call mom", done: false },
];

const items = tasks.map(t => "<li>" + (t.done ? "✓ " : "") + t.title + "</li>");
console.log("<ul>" + items.join("") + "</ul>");

Output

<ul><li>✓ Water plants</li><li>Pay rent</li><li>Call mom</li></ul>

map turns each task object into one li string and join glues them together. In the JSX version React does the joining for you, which is the only real difference between this and the component from the previous block.

The ternary is the same expression you would embed in JSX, and it has to be an expression rather than an if for exactly the reason from lesson 2-2. Braces in JSX accept expressions only, so a ternary is the standard way to choose between two pieces of output inline.

Note that the callback returns a value on every path, including the not-done case where the ternary contributes an empty string. A callback that returned nothing for some element would put undefined into the array, and in JSX that renders as nothing at all, which makes it a quiet bug.

Three tasks in, three list items out, in the original order. map preserves order, so the screen order is the array order, and changing the display order means sorting a copy of the array rather than doing anything special in the JSX.

Filtering before mapping

filter keeps only the scores at 70 or above, and map turns each survivor into a list item.

const scores = [
  { name: "Amara", points: 91 },
  { name: "Ben", points: 55 },
  { name: "Chen", points: 78 },
];

const rows = scores
  .filter(s => s.points >= 70)
  .map(s => "<li>" + s.name + ": " + s.points + "</li>");

console.log("<ol>" + rows.join("") + "</ol>");

Output

<ol><li>Amara: 91</li><li>Chen: 78</li></ol>

Chaining is what makes this readable, reading as filter then map, in that order. Ben's 55 never reaches the map callback at all, so the mapping step never has to think about whether to skip anything.

The order of the chain matters. Mapping first and filtering second would work here and would mean filtering strings rather than objects, so the filter callback would have to parse the number back out of the markup it just built.

Note that filter returns a shorter array while map returns one of equal length, which is the division of labor between them. Trying to do both jobs in one map is the usual beginner shape, and it produces empty strings or undefined entries where the skipped items were.

This chain is exactly what a filtered task list looks like in JSX, where the whole expression sits inside braces. Unit 9's task tracker builds that, and by then the only new part is the key attribute.

Reading a mapped list in JSX

const nums = [1, 2, 3];
return <div>{nums.map(n => <b key={n}>{n * 10}</b>)}</div>;

The screen shows 10, 20, and 30 in bold, rendered as three separate bold elements with no space between them, so it reads as 102030.

map produces the array [<b>10</b>, <b>20</b>, <b>30</b>], and JSX renders an array of elements in order, so the div contains the three bold elements back to back.

The lack of spacing surprises people. JSX inserts nothing between array elements, so any separator has to be part of the elements themselves or come from CSS, which is why real lists use block-level tags like li that stack on their own.

key={n} uses the number itself as the key, which is acceptable here because the numbers are unique. The next lesson explains what keys are for and why the array index is usually the wrong choice.

Note that {n * 10} is a second set of braces nested inside the first. The outer braces embed the map call in the JSX, and the inner ones embed the arithmetic in the b element, and both are the same rule applied at two levels.