DevNotes

DevNotes

@devnotes ·

React Part 6: Lists, keys and conditions

Most screens are lists of something, whether it's messages or search results.

From an array to a list

Remember map from JavaScript Part 6? In React, it turns data into elements:

JSX
const courses = [  { id: 1, title: "HTML basics", lessons: 5 },  { id: 2, title: "CSS layouts", lessons: 7 },  { id: 3, title: "JavaScript", lessons: 10 },];
function CourseList() {  return (    <ul>      {courses.map((course) => (        <li key={course.id}>          {course.title}: {course.lessons} lessons        </li>      ))}    </ul>  );}

map returns an array of <li> elements, and React draws each of them in place.

Keys

Every item in a list needs a key prop: a value that identifies that item, and stays the same for it, every time.

React uses keys to match items up between one draw and the next. When an item is added, removed or moved, the keys tell React which item is which, so it updates only what changed, and keeps each item's state, like text typed into a box inside it, with the right item.

Good keys come from your data: an id from the database, a username, a product code. They need to be:

  • Unique among the items in that list.
  • Stable: the same item keeps the same key forever.

You'll be tempted to use the index from map((item, index) => ...). That's only safe if items are only ever added at the end, and never removed, reordered or filtered. Otherwise React mixes items up, and you get bugs like the wrong row staying ticked after a delete. And never make keys with Math.random() while drawing: every draw then gets new keys, so React throws everything away and starts over each time.

Forget the key and React warns you in the console. Don't ignore that warning.

Showing things only sometimes

Three patterns cover nearly everything. && shows something, or nothing:

JSX
{unread > 0 && <span className="badge">{unread}</span>}

A ternary picks one thing or the other:

JSX
{isLoggedIn ? <Avatar /> : <button>Log in</button>}

An early return swaps in a different screen entirely:

JSX
function Cart({ items }) {  if (items.length === 0) {    return <p>Your cart is empty.</p>;  }  return <CartList items={items} />;}

One trap with &&: if the left side is the number 0, React puts a "0" on the screen. {messages.length && <MessageList />} shows a stray 0 when there are no messages. Write {messages.length > 0 && <MessageList />} instead, so the left side is a real true or false.

Work out the data before drawing

Do the filtering and sorting above the return:

JSX
function Results({ items, query }) {  const matches = items.filter((item) =>    item.title.toLowerCase().includes(query.toLowerCase())  );
  if (matches.length === 0) {    return <p>No results for "{query}".</p>;  }
  return (    <ul>      {matches.map((item) => (        <li key={item.id}>{item.title}</li>      ))}    </ul>  );}

Notice matches isn't kept in state. It's worked out from the props every time the component draws. If a value can be calculated from what you already have, calculate it. Storing it as separate state means keeping two things in sync, and sooner or later they drift apart.

Try it

Show a list of five books with a search box above it that filters them by title as you type, and a "No books found" message when nothing matches. Use each book's id as its key.


Next: React Part 7: Load data with useEffect

reactjavascriptwebdevbeginners

Join WIK to like and reply to DevNotes and see more like this.