---
tags: ironhack, lecture,
---
<style>
.markdown-body img[src$=".png"] {background-color:transparent;}
.alert-info.lecture,
.alert-success.lecture,
.alert-warning.lecture,
.alert-danger.lecture {
box-shadow:0 0 0 .5em rgba(64, 96, 85, 0.4); margin-top:20px;margin-bottom:20px;
position:relative;
ddisplay:none;
}
.alert-info.lecture:before,
.alert-success.lecture:before,
.alert-warning.lecture:before,
.alert-danger.lecture:before {
content:"👨🏫\A"; white-space:pre-line;
display:block;margin-bottom:.5em;
/*position:absolute; right:0; top:0;
margin:3px;margin-right:7px;*/
}
b {
--color:yellow;
font-weight:500;
background:var(--color); box-shadow:0 0 0 .35em var(--color),0 0 0 .35em;
}
.skip {
opacity:.4;
}
</style>

# React | List & Keys
## Learning Goals
After this lesson, you will be able to:
- Display a `list` in a React app
- Get better knowledge on how React represents changes in the DOM
- Learn about the `key` prop
- Compose components and split responsibilities of components
## Introduction
:::info lecture
En hbs, nous utilisions `{{#each}}` pour afficher une liste... Quel est l'équivalent en JSX ?
:::
If you need to display **an array of data** in a React component, how would you do it? What you do is you **transform your array** of data to an **array of elements**.
:::info lecture
Reprenons notre application précédente `my-new-app`...
:::
Let's demonstrate this on example. Keep working in our `my-new-app` project. Inside components folder create `ListDemo.js` file. This file will be example of *functional component* and it will have multiple exports. So let's imagine you have an array of numbers in this component and we need to take that array and turn it into an *array of list item HTML elements* like:
:::info lecture
Créons un fichier `src/components/ListDemo.js` exportant du JSX :
:::
```jsx
// src/components/ListDemo.js
import React from 'react';
const numbers = [1, 2, 3, 4, 5, 6];
// array of list item HTML elements that needs to be displayed
export const listItems = [
<li>1</li>,
<li>2</li>,
<li>3</li>,
<li>4</li>,
<li>5</li>,
];
```
:::info lecture
☝️Plus précisément, ce module exporte un tableau de JSX
[](https://babeljs.io/repl#?browsers=&build=&builtIns=false&spec=false&loose=false&code_lz=NoKABGA8A2CWB8BGSB6O8A04roEyvSwhgQGYCEicEAWCzbE-AVnqwF0g&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=es2015%2Creact%2Cstage-2&prettier=false&targets=&version=7.8.4&externalPlugins=)
:::
It turns out we can take that array of HTML elements and display them directly in our *App.js*. Steps we need to take are importing `listItems` in App.js file and interpolating it into the DOM.
:::info lecture
Importons ce named-exported module et tentons de l'imprimer dans une `<ul>` L10 **comme n'importe quelle variable** :
:::
```jsx=
// src/App.js
...
import { listItems } from './components/ListDemo'; {/* 👈 */}
class App extends Component {
render() {
return (
<div className="App">
<Header></Header>
<ul>{ listItems }</ul> {/* 👈 */}
</div>
);
}
}
export default App;
```
Notice how we *imported* `listItems`: we used `{ }` and the `name is exactly the same` as the one in the *ListDemo.js* file. This is the way to import so called `named` exports and this tells us that we will have more than one export from this file so that's why we didn't use `export default` way. Looking from the perspective of better efficiency, there's no benefit of using *named* vs. *default* exports but there's huge benefit in better organization of our code, especially in large apps.
:::info lecture
<span style="font-size:300%">🙌</span>
On peut donc imprimer des tableau de JSX : les éléments de ce tableau seront imprimés les uns à la suite des autres -- **comme un peu dans un boucle**
:::
Okay, this is pretty much hard coded so far and we still have the same question:
how can we turn an array of `1`, `2`, `3` into an array of `<li>1</li>`, `<li>2</li>`, `<li>3</li>`? Well, we can call our old friend, `.map()` of course!
Comment out hard coded *listItems* and add this code to *ListDemo.js*.
:::info lecture
Par conséquent, on va pouvoir se servir de `.map()` pour transformer un tableau de valeurs `[1, 2, 3]` en un tableau de JSX `[<li>1</li>, <li>2</li>, <li>3</li>]` :
:::
```jsx
// src/components/ListDemo.js
...
export const listItems = numbers.map(oneNumber => <li>{oneNumber}</li> );
```
Nothing's changed in `App.js` and we still see the same result in our browser. This is getting better and better!
:::info
You may notice that the `.map()` doesn't have the normal curly braces `{}` and doesn't have a `return`. You'll find that in many React examples they will leave those out. Remember the syntax of an arrow function allows that!
:::
### List key
If we open the console to check on our result... oops! There's an error here:
:::info lecture
Si nous ouvrons devTools, nous allons voir apparaître des `warning` React :
:::
:::danger
Warning: Each child in an array or iterator should have a unique "key" prop.
:::
This means that our `<li>` tags need a key prop. **Keys are what React uses to identify DOM elements in an array when it checks to see if any of them need to be updated.**
If the keys change or there are no keys, then the **entire DOM is destroyed and recreated**, even if no real changes have ocurred to the list. As you may imagine, it's wasteful to have React constantly recreate the DOM if nothing has changed. Keys help React identify DOM elements so that it can see if that element has changed or not.
Let’s assign a key to our list items inside `numbers.map()` and fix the missing key issue.
:::info lecture
Dans une liste, chaque élément doit porter une `prop` spéciale : `key={id}`, `id` devant être une valeur unique.
:::
:::info lecture
Cette restriction est utile à React lors du shadow-diff pour savoir quel(s) éléments de la liste ont changé entre 2 rendus.
:::
:::info lecture
On peut éventuellement utiliser l'`index` de l'élément du tableau (même s'il est préférable d'utiliser un vrai ID) :
:::
```jsx
// src/components/ListDemo.js
...
export const listItems = numbers.map((oneNumber, index) =>
<li key={index}>{oneNumber}</li>
);
```
Still no changes in *App.js* and there's no error in our console. Great!
### Composing a list component
:::info lecture
Tentons maintenant d'afficher une liste plus sophistiquée : une liste de films...
:::
Now let's see a similar examples with an array of objects instead. Here's a list of some of our favorite movies:
:::info lecture
Dans le même fichier `ListDemo.js`, déclarons notre tableau de films `movies` (nos datas 🤓) :
:::
```jsx
// src/components/ListDemo.js
...
const movies = [
{ title: "Jurassic Park", director: "Steven Spielberg" },
{ title: "Sharknado", director: "Anthony C. Ferrante" },
{ title: "Titanic", director: "James Cameron" }
];
```
As you can see, we have the list of favourite movies as an array of objects with two properties `title` and `director`. Normally we would display it in a series of unordered list items, but since we use React, let's go and create component that displays the list.
What we want to do here is *separating the responsibility* of iterating through the list from the rendering the item itself. This will allows us to, for example, filter the movies without altering the way items look.
So let's update our *ListDemo.js*:
:::info lecture
Exportons un function-component `MoviesList` retournant du JSX. **Chaque film sera représenté par un composant `Card` dans chaque `li`**:
:::
```jsx
// src/components/ListDemo.js
...
import Card from './Card' {/* 👈 */}
...
const movies = [
{ title: "Jurassic Park", director: "Steven Spielberg" },
{ title: "Sharknado", director: "Anthony C. Ferrante" },
{ title: "Titanic", director: "James Cameron" }
];
{/* 👇 */}
export const MoviesList = () =>{
return (
<ul>
{movies.map(item => (
<li key={index}>
<Card title={item.title} director={item.director} />
</li>
))}
</ul>
)
};
```
:::success lecture
☝️ Il s'agit ici d'un `named-export` car plusieurs *choses* sont exportées.
:::
Inside our *components* folder let's create `Card.js` file.
:::info lecture
Sans oublier notre nouveau composant `Card` pour représenter un film :
:::
```jsx
// src/components/Card.js
import React from 'react';
const Card = (props) => {
const { title, director } = props;
return (
<div>
<h2>{title}</h2>
<p>Director: {director}</p>
</div>
)
};
export default Card;
```
And let's update `App.js`:
:::info lecture
Enfin, instancions cette `MoviesList` depuis note `App` :
:::
```jsx
// src/App.js
...
import { listItems, MoviesList } from './components/ListDemo.js'; {/* 👈 */}
class App extends Component {
render() {
return (
<div className="App">
<Header />
<ul className="list-style">{ listItems }</ul>
{/* 👇 */}
<div>
<MoviesList />
</div>
</div>
);
}
}
```
:::info lecture
<span style="font-size:300%">🙌</span>
All done !
:::
#### `DynamicMoviesList`
:::info lecture
Et **si nous rendions cette liste dynamique** ? C'est à dire qu'en tant qu'utilisateur, nous puission supprimer/ajouter des films à la volée !
:::
This is all good but there's a lot of things that can be improved. First, we can't change anything because our component is stateless, so we can't make updates unless we hard code them. Second, even passing the props to *Card* component is not the most efficient, we can improve that too. So let's do this - let's leave this stateless component as example and let's move forward creating `stateful` component and let's give some dynamic to our app.
:::info lecture
Ne touchons pas à ce que nous avons fait pour notre liste statique de films. Créons :
```shell
$ mkdir src/components/dynamicListsDemo
$ touch src/components/dynamicListsDemo/DynamicMoviesList.js
$ touch src/components/dynamicListsDemo/ImprovedCard.js
```
:::
Inside our `components` folder let's create another folder `dynamicListsDemo` and inside this folder let's create two files/components: `DynamicMoviesList.js` and `ImprovedCard.js`.
In our `DynamicMoviesList.js` we will introduce class with the same name and since we will be keeping track of the state and its changes, our class will need to have `constructor()` in which we will set our original state using `this.state`, with accent on `this` keyword. In this context keyword `this` is referring to `Component` that extends our class. We encourage you to try setting the state to some other array of movies.
:::info lecture
Nous en profiterons pour définir d'autres datas de films pour ne pas confondre. Remarquez également la présence d'une propriété `id` comme si la data provenait d'une DB 🤓.
:::
```js
movies: [
{ id: "fUbJPciPq", title: "The Godfather", director: "Francis Coppola" },
{ id: "EXN9npPlo", title: "Star Wars", director: "Rian Johnson" },
{ id: "lecqGHnAb", title: "The Shawshank Redemption", director: "Frank Darabont" }
]
```
If you blindly follow our instructions, you just run into the wall and the reason for that is ES6 syntax that wants us to use `super()` keyword (in this context, we can freely say `super as method`) in order to use `this` keyword inside the constructor. This all might seem too complicated and hypothetical, so let's add some code to our *DynamicMoviesList.js*:
:::info lecture
Ecrivons notre composant `DynamicMoviesList` :
:::
```jsx
// src/components/dynamicListsDemo/DynamicMoviesList.js
import React, { Component } from 'react';
class DynamicMoviesList extends Component {
constructor(props) {
super(props);
this.state = {
movies: [
{ id: "fUbJPciPq", title: "The Godfather", director: "Francis Coppola" },
{ id: "EXN9npPlo", title: "Star Wars", director: "Rian Johnson" },
{ id: "lecqGHnAb", title: "The Shawshank Redemption", director: "Frank Darabont" }
]
}
}
render() {
// leave this console.log() so we can keep track of our state inside our browser's console
console.log('🍿movies', this.state.movies);
return (
<div>
{this.state.movies.map(item => {
// some code to be rendered will come here
})}
</div>
);
}
}
export default DynamicMoviesList;
```
:::success lecture
Nous aurions pu tout aussi bien nous passer d'un `constructor` ici pour définir le `state`, grâce aux [class properties](https://babeljs.io/docs/en/babel-plugin-proposal-class-properties) supportés par Babel dans [CRA](https://create-react-app.dev/docs/supported-browsers-features/#supported-language-features) :
```jsx
...
class DynamicMoviesList extends Component {
// 👇
state = {
movies: [
{ id: "fUbJPciPq", title: "The Godfather", director: "Francis Coppola" },
{ id: "EXN9npPlo", title: "Star Wars", director: "Rian Johnson" },
{ id: "lecqGHnAb", title: "The Shawshank Redemption", director: "Frank Darabont" }
]
};
render() {
...
}
...
}
```
:::
So if we look back to our notes after we finished displaying movies on 'hard coded' way, we said we can improve on adding the state (okay, done!), keeping the track on state's changes (getting there) and the way we are rendering the *Card* component (let's improve that right now).
Open previously created `ImprovedCard.js` and let's add some code:
:::info lecture
Ecrivons notre composant `ImprovedCard` :
:::
```jsx
// src/components/dynamicListsDemo/ImprovedCard.js
import React from 'react';
const ImprovedCard = (props) => {
return (
<div className="movies-list-item">
<h2>{props.title}</h2>
<p>Director: {props.director}</p>
</div>
)
};
export default ImprovedCard;
```
Comparing our *not so perfect* `Card.js` and *quite perfect* `ImprovedCard.js`, we notice that instead of passing as argument all the properties (which can be numerous), we can easily pass just the props object. This will also lead to simplifying the way pass these props when we render `ImprovedCard` component. Okay, let's do it:
:::info lecture
Intégrons `ImprovedCard` à notre `DynamicMoviesList` :
:::
```jsx
// src/components/dynamicListsDemo/DynamicMoviesList.js
import React, { Component } from 'react';
import ImprovedCard from './ImprovedCard.js'; // 👈
...
render() {
console.log('🍿movies', this.state.movies);
return (
<div>
{
this.state.movies.map(item => {
return <ImprovedCard key={item.id} {...item} />; // 👈
})
}
</div>
);
}
```
:::info lecture
Remarquons que l'on préfère ici utiliser l'`id` du film plutôt que l'`index` en tant que `key`!
:::
:::info lecture
Remarquons `{...item}` qui imprime chacune des clef/valeurs de l'object `item` en tant que `props` 😲, ce qui revient à :
```jsx
<ImprovedCard id="fUbJPciPq" title="The Godfather" director="Francis Coppola" />
```
Ce pattern s'appelle le [`spread-attributes`](https://reactpatterns.com/#jsx-spread-attributes).
:::
Using **spread operator** as super cool ES6 feature, we can pass the whole `item` object as the props down to `<ImprovedCard />` component.
At this point, the only thing we are missing is to render `<DynamicMoviesList />` in *App.js*.
:::info lecture
Enfin, rendons notre `<DynamicMoviesList />` dans notre `App` :
:::
```jsx
// App.js
...
import DynamicMoviesList from './components/dynamicListsDemo/DynamicMoviesList';
class App extends Component {
render() {
return (
<div className="App">
<Header />
<ul className="list-style">{ listItems }</ul>
<div>
<MoviesList />
</div>
<hr />
<DynamicMoviesList /> {/* 👈 */}
</div>
);
}
}
export default App;
```
Go and check your browsers. So far so good.
---
:::info lecture
Créons maintenant un **bouton delete** dans chaque `<ImprovedCard>` qui ira supprimer de nos state le film et provoquera le re-rendu...
:::
Let's see what else we can improve or add. We mentioned *keeping
track on state changes*. Let's add `Delete` button to our `ImprovedCard.js` and let's attach `onClick` listener to it.
```jsx
// components/dynamicListsDemo/ImprovedCard.js
import React from 'react';
const ImprovedCard = (props) => {
return (
<div className="movies-list-item">
<h2>{props.title}</h2>
<p>Director: {props.director}</p>
<button onClick={props.clickToDelete}>Delete</button> {/* 👈 */}
</div>
)
};
export default ImprovedCard;
```
We see that *onClick* is looking for props `clickToDelete` so let's add some code in our *DynamicMoviesList.js*.
```jsx
// components/dynamicListsDemo/DynamicMoviesList.js
...
deleteMovieHandler = (id) => {
const moviesCopy = this.state.movies; // ⚠️
const movieIndex = moviesCopy.findIndex(el => el.id === id)
moviesCopy.splice(movieIndex, 1);
this.setState({
movies: moviesCopy
})
}
render() {
console.log('🍿movies', this.state.movies);
return (
<div>
{this.state.movies.map(item => {
return <ImprovedCard key={item.id} {...item} clickToDelete={() => this.deleteMovieHandler(item.id)} />
})}
</div>
);
}
```
:::info lecture
Nous passons à `<ImprovedCard>` une fonction. Cette fonction sera celle qui sera exécutée lors du click sur le bouton, cf `ImprovedCard.js` :
```jsx
<button onClick={props.clickToDelete}>Delete</button>
```
Cette fonction fera alors elle-même appel à `deleteMovieHandler` en lui passant l'id du film en paramètre.
:::
Let's start from `clickToDelete` props that's being passed into `<ImprovedCard />` component. It is actually passing a function that, once clicked, will call `deleteMovieHandler` method with the corresponding movie's `id` as argument. We have to pass the `id` since _that's the only field we're certain we can use to make distinguish between different objects_.
For now, our`deleteMovieHandler()` is making the copy of the movies array, deleting the one on the index that we passed and setting the state to the newly updated array. It all works BUT there's something we can't ignore!
:::danger
:warning: We are actually _mutating original state_ and we **shouldn't** be doing that. So what we can change? We can make a **copy of the state**, or better saying, part of the state that's going to be changed so we **update state in immutable way**. We can use **spread operator** to make copy of our state:
:::
:::warning lecture
ATTENTION : Nous ne faisions jusque là pas de copie de `this.state.movies` !!!
:::
```jsx
// src/components/dynamicListsDemo/DynamicMoviesList.js
...
deleteMovieHandler = (movieIndex) => {
const moviesCopy = [...this.state.movies]; // 👈
const movieIndex = moviesCopy.findIndex(el => el.id === id)
moviesCopy.splice(movieIndex, 1);
this.setState({
movies: moviesCopy
})
}
...
```
So now we are mutating copy of the state and we are using `.setState()` to update the state itself.
You might notice we added some *CSS* classes, so here is the css part. Feel free to add it in your *App.css*.
```css
.list-style {
text-align: left;
}
.movies-list-item {
width: 400px;
border: 1px black solid;
padding: 10px;
cursor: pointer;
margin: 5px auto;
}
```
#### Practice time!
:::info lecture
Ajouter un bouton à `<DynamicMoviesList />` qui rajoutera le film Harry potter.
:::
Inside `<DynamicMoviesList />` create button that will *Add Harry Potter* movie to the list. Easy right? But make sure you don't mutate state directly!
## Summary
Working with lists in React asks for some extra attention regarding to method we use, which is `map()`, making sure we pass `key` prop to tell React which component should be updated and keeping an eye on **immutability** of the state, which we achieve by making its copy that gets changes.
## Extra Resources
- [React Lists and Keys](https://reactjs.org/docs/lists-and-keys.html)
- [JSX Spread Attributes](https://reactpatterns.com/#jsx-spread-attributes)
- [Arrays in React](https://medium.com/byte-sized-react/component-arrays-in-react-a46e775fae7b)