Rendering lists
Turn arrays of data into lists of components with map and filter, and give each item a stable key.
In the props lesson you wrote <BookCard book={...} /> once for every book. That works for three books, but a real reading list could hold fifty, and the books change as you add and remove them. You can't hand-write a line of JSX for data you don't know yet.
The fix is to keep your books in an array and let JavaScript produce one component per item. You already met the tool for this on Day 1: the array method map.
From an array to elements with map
map calls a function for every item in an array and collects the results into a new array. If that function returns JSX, you get an array of JSX, and React knows how to render an array of JSX:
You'll usually skip the items variable and write the map call straight inside the JSX braces. Here is the same idea with book objects and a BookCard component:
Open data.js and add a fourth book. The page updates without touching App.js at all. That is the payoff: the JSX describes how one book looks, and the data decides how many there are.
Showing only some items with filter
Often you want part of a list, such as only the books you're reading right now. Use filter to make a smaller array first, then map over the result:
filter and map both return new arrays. They never change books itself. That matters, as you'll see in the next lesson on pure components.
Keeping list items in order with key
Every example so far has put a key on the outermost element inside map. Here is what happens without one. Open the console:
The page still looks right, but React logs a warning saying each child in a list should have a unique key prop. Add key={book.id} to the <li> and the warning goes away.
A key is a name tag for a list item. Later, when the list changes (you add a book, delete one or sort them), React compares the old list with the new one. Keys let it match "this item now" with "that item before", so it updates the right thing instead of guessing by position.
The rules for keys
- Keys come from your data. Books from a database already have IDs. For books you create in the browser, you'll generate an ID once with
crypto.randomUUID()when the book is added (on Day 3). - Keys must be unique among siblings. Two items in the same list can't share a key. Items in different lists can reuse the same keys.
- Keys must not change between renders. Never generate them during rendering, as in
key={Math.random()}. A new key every time makes React throw away and rebuild every item on each render. - Put the key on the outermost element returned from
map. Inbooks.map((book) => <BookCard key={book.id} book={book} />)the key goes onBookCard, not on something insideBookCard.
Your components do not receive key as a prop. It is a hint meant only for React. If BookCard needs the ID itself, pass it separately: <BookCard key={book.id} id={book.id} book={book} />. In this course BookCard already receives the whole book, so it can read book.id directly.
Deep diveKeys on a group of elements
Sometimes each item renders more than one element without a wrapper, using a Fragment. The short <>...</> syntax can't take a key, so import Fragment and write it out in full:
import { Fragment } from 'react';
function Glossary({ terms }) {
return (
<dl>
{terms.map((term) => (
<Fragment key={term.id}>
<dt>{term.word}</dt>
<dd>{term.meaning}</dd>
</Fragment>
))}
</dl>
);
}A Fragment adds nothing to the page. It only groups the dt and dd so the key has somewhere to live.
Which is the best key for a book in the Reading List?
Extracting a BookList component
The map call is a good candidate for its own component. BookList takes an array of books as a prop and renders one BookCard each. It can also handle the empty case, which combines today's lessons nicely:
Now App doesn't need to know how a list is drawn. It hands over the data, and BookList does the rest.
Shelves by status
Use filter and map to render three sections: "Want to read", "Reading" and "Done". Each section lists the titles of the books with that status, and each li needs a proper key. As a bonus, make a small Shelf component so you don't repeat the same JSX three times.
Show solution
Shelf takes a heading and an already-filtered array. App filters once per status and passes the result down.
The keys go on the li because that is the outermost element returned from map. The three Shelf components don't need keys, because they are written out by hand, not produced by map.
BookCard returns an article element, and BookList renders books.map(book => <BookCard book={book} />). Where should the key go?
Day 2 progress