Course: Section

Get Started with Modern React: Step by Step

Episode: Title

S02・V15: Lists and Keys (Part 2)

Date Created: July 17th, 2019
Last Updated: 27 days ago

Objectives
  1. We will show you how to extract a component with a key.
  2. We will demonstrate embedding “map()” within a JSX container.
  3. We will explain that keys must only be unique among their siblings.
Watch Video
Duration: 5m 43s

Extracting Components with Keys

Keys only make sense in the context of the surrounding array.

Starting from the TodoList example from the last video:

import React from "react";
import ReactDOM from "react-dom";

const todos = [
  'Learn React',
  'Wash dishes',
  'Make bed',
];

const TodoList = props => {
  const { todos } = props;
  const listItems = todos.map((todo, index) => <li key={index}>{todo}</li>);
  return <ul>{listItems}</ul>;
};

ReactDOM.render(<TodoList todos={todos} />, document.getElementById("root"));

We will extract a ListItem component from the TodoList component. We will destructure todo from props. And we will return the li from the TodoList component. Now we will add the ListItem in its place.

import React from "react";
import ReactDOM from "react-dom";

const todos = [
  'Learn React',
  'Wash dishes',
  'Make bed',
];

const ListItem = props => {  const { todo } = props;  return (    <li key={index}>{todo}</li>  );};
const TodoList = props => {
  const { todos } = props;
  const listItems = todos.map((todo, index) => <ListItem todo={todo} />);  return <ul>{listItems}</ul>;
};

ReactDOM.render(<TodoList todos={todos} />, document.getElementById("root"));

In our ListItem component, index is not defined. We may be tempted to use the todo text as the key instead.

const ListItem = props => {
  const { todo } = props;
  return (
    <li key={todo}>{todo}</li>  );
};

However, in Chrome’s Developer Console, we get a warning that each child in a list should have a unique “key” prop. Remember that keys only make sense in the context of the surrounding array.

We will remove the key from the ListItem component, since it is not in the context of the todos array. Instead, we will add the key within ListItem element within the TodoList component, which is in the context of the todos array.

const ListItem = props => {
  const { todo } = props;
  return (
    <li>{todo}</li>  );
};

const TodoList = props => {
  const { todos } = props;
  const listItems = todos.map((todo, index) =>
    <ListItem key={index} todo={todo} />  );
  return <ul>{listItems}</ul>;
};

We can now see that the warning has disappeared. This is the correct way to specify the “key”. A good rule of thumb is that elements inside the map() need keys.

Embedding map() in JSX

JSX allows embedding any expression in the curly braces of a JSX container, so we could inline the map() result. Sometimes this results in clearer code, but this style can also be abused. Like in JavaScript, it is up to you to decide whether it is worth extracting a variable for readability. Keep in mind that if the map() body is too nested, it might be a good time to extract a component.

const ListItem = props => {
  const { todo } = props;
  return (
    <li>{todo}</li>
  );
};

const TodoList = props => {
  const { todos } = props;
  return (
    <ul>      {todos.map((todo, index) =>        <ListItem key={index} todo={todo} />      )}    </ul>  );
};

Unique among Siblings

Keys used within arrays should be unique among their siblings. However, they don’t need to be globally unique. We can use the same keys when we produce two different arrays.

We will render a Blog element and pass in posts as a prop.

import React from "react";
import ReactDOM from "react-dom";

ReactDOM.render(<Blog posts={posts} />, document.getElementById("root"));

Now, we will add some hard-coded posts.

import React from "react";
import ReactDOM from "react-dom";

const posts = [  { id: 1, title: "Hello World", content: "Welcome to learning React!" },  { id: 2, title: "Installation", content: "Use yarn to install React." }];
ReactDOM.render(<Blog posts={posts} />, document.getElementById("root"));

Now, we will define the Blog component. First, we will add a sidebar. Inside sidebar, we will map over each post and display it in an unordered list. We will add the post.id as the key. Next, we will add a content section, which will map over each post, and display its title and its content. As we did in sidebar, we will add the post.id as the key. We will return the sidebar and the content, separated by a hr.

import React from "react";
import ReactDOM from "react-dom";

const Blog = props => {  const sidebar = (    <ul>      {props.posts.map(post => (        <li key={post.id}>{post.title}</li>      ))}    </ul>  );  const content = props.posts.map(post => (    <div key={post.id}>      <h3>{post.title}</h3>      <p>{post.content}</p>    </div>  ));  return (    <div>      {sidebar}      <hr />      {content}    </div>  );};
const posts = [
  { id: 1, title: "Hello World", content: "Welcome to learning React!" },
  { id: 2, title: "Installation", content: "Use yarn to install React." }
];

ReactDOM.render(<Blog posts={posts} />, document.getElementById("root"));

Note that we have used the same key in both the sidebar and content sections.

Keys serve as a hint to React, but they do not get passed to your components. That means that the key prop cannot be destructured from props in a component. If you need the value you passed as a key in your component as well, then you will need to explicitly pass it as a prop with a different name.

Summary

We showed you how to extract a component with a key. We demonstrated embedding map() within a JSX container. And, we explained that keys must only be unique among their siblings.

Next Up…

In the next video, we will talk about using forms in React.