Lesson 7

Rendering Lists

Rendering list basics in React.

In React, There will often be times where you need to render the same component again but with different data. To do this you will have you data in the format of an array. JavaScript array methods will help you manipulate these arrays. For more info on JavaScript array methods check this out.

Rendering Data in Arrays

In App.jsx we have one Card component, but say we have two more Card components. So three total.

// App.jsx
<Section>
  <Card card={card} backgroundColor="rebeccapurple" favorited={true} />
  <Card card={card} backgroundColor="midnightblue" favorited={false} />
  <Card card={card} backgroundColor="deeppink" favorited={true} />
</Section>

That's not too bad, but let's say we have more Card components we need to render that all have different data. Then this might get problematic. So let's put this data into an array. Luckily there is already an array called cards in the variables.js file that is being exported. Import cards into App.jsx.

// App.jsx
import { cards } from "./variables";

Then delete the Card components in the Section component. Here we are going to use a map array method on the cards array. In between the Section tags add the following:

// App.jsx
<Section>
  {cards.map((card) => {
    return (
      <Card card={card} backgroundColor="rebeccapurple" favorited={true} />
    );
  })}
</Section>

Note: The JavaScript array map method creates a new array by applying logic that iterates over each element of the array. Here's an example:

const numbers = [1, 2, 3, 4, 5];
 
const doubled = numbers.map((number) => {
  return number * 2;
});
 
console.log(numbers); // [1, 2, 3, 4, 5]
console.log(doubled); // [2, 4, 6, 8, 10]

You may have noticed that the cards array data is not fully being rendered as expected. We will need to update the Card component props to reflect this.

// App.jsx
<Section>
  {cards.map((card) => {
    return <Card card={card} />;
  })}
</Section>
// Card.jsx
function Card({ card }) {
  const capitalize = (string) => {
    return string.toUpperCase();
  };
 
  return (
    <div className="card" style={{ backgroundColor: card.backgroundColor }}>
      <img src={card.src} />
      <>
        <h2>
          {capitalize(card.title)} <span>{card.favorited && "⭐️"}</span>
        </h2>
        <p>{card.description}</p>
      </>
    </div>
  );
}

Now we should see the cards array data being rendered as expected.

Rendering Filtered Arrays

Let's say you have a list of data you want to filter through condition. You need to get data that has a certain boolean value, role, or product type. We can filter through array data with the JavaScript array filter method. Let's try an example in the TodoList component. In TodoList.jsx, import todos from variables.js.

// TodoList.jsx
import { todos } from "../variables";

Then before we return the markup in TodoList, we will create a incompleteTodos array using the filter array method.

// TodoList.jsx
function TodoList() {
    const incompleteTodos = todos.filter((todo) => {
        return !todo.completed;
    });
 
    return ( ... );
}

Note: The JavaScript filter() array method creates a new array containing only the elements of the original array that satisfy a given condition. So only elements that return the boolean value true will be contained in the new array. Here's an example:

const numbers = [1, 2, 3, 4, 5];
 
// Use filter to create a new array with only even numbers
const evens = numbers.filter((number) => {
  return number % 2 === 0;
});
 
console.log(evens); // [2, 4]

Note: Here we are also using the ! operator, also known as the logical NOT operator. This inverts the truthiness of a value. Here's an example:

const isLoggedIn = false;
 
// Use the ! operator to invert the value
if (!isLoggedIn) {
  console.log("User is not logged in.");
}

Now we can update the TodoList to use the incompleteTodos array and map over the TodoItem component:

return (
  <div>
    <h2>Todo List</h2>
    <ul>
      {incompleteTodos.map((todo) => {
        return <TodoItem name={todo.name} completed={todo.completed} />;
      })}
    </ul>
  </div>
);

Now we should only be rendering the todos that have their completed value set to false.

Keys

If you open up your developer tools in your browser and look at the Console tab, you should see this warning Each child in a list should have a unique "key" prop. twice. And it says to Check the render method of App and TodoList. To fix this, you need to give each array item a key prop that unquely identifies it from every other item in that array. Add the following in App.jsx to fix this:

// App.jsx
<Section>
  {cards.map((card) => {
    return <Card key={card.id} card={card} />;
  })}
</Section>

And in TodoList.jsx:

<ul>
  {incompleteTodos.map((todo) => {
    return (
      <TodoItem key={todo.id} name={todo.name} completed={todo.completed} />
    );
  })}
</ul>

If you look in the Console again, those warnings should be gone. So why do we need the key prop? It is React's way off keeping track of each component. This is important if the items in your array move from getting sorted, new items get inserted, or items get deleted. This helps React update the correct component. A few rules to follow with keys:

  • JSX elements inside a map must always have keys.
  • Keys must be unique in an array that is being rendered.
  • Keys must not change. Do not generate them while rendering.