Vous aurez souvent besoin d’afficher des composants similaires à partir d’une collection de données. Vous pouvez utiliser les méthodes des tableaux JavaScript pour manipuler un tableau de données. Dans cette page, vous utiliserez filter() et map() avec React pour filtrer et transformer vos données afin de produire un tableau de composants.

Vous allez apprendre

  • Comment afficher des composants à partir d’un tableau de données en utilisant le map() de JavaScript
  • Comment n’afficher que certains composants précis en utilisant le filter() de JavaScript
  • Quand et pourquoi utiliser des clés React

Afficher des données à partir de tableaux

Disons que vous avez une liste de contenus.

<ul>
<li>Creola Katherine Johnson : mathématicienne</li>
<li>Mario José Molina-Pasquel Henríquez : chimiste</li>
<li>Mohammad Abdus Salam : physicien</li>
<li>Percy Lavon Julian : chimiste</li>
<li>Subrahmanyan Chandrasekhar : astrophysicien</li>
</ul>

La seule différence entre ces éléments de liste, c’est leur contenu, c’est-à-dire leurs données. Vous aurez souvent besoin d’afficher plusieurs instances d’un même composant mais avec des données différentes lorsque vous construirez des interfaces : listes de commentaires, galeries d’images de profils, etc. Dans de telles situations, vous pourrez stocker les données dans des objets et tableaux JavaScript et utiliser des méthodes comme map() et filter() pour produire des listes de composants à partir de ces données.

Voici un court exemple de génération d’une liste d’éléments à partir d’un tableau :

  1. Déplacez les données dans un tableau
const people = [
'Creola Katherine Johnson : mathématicienne',
'Mario José Molina-Pasquel Henríquez : chimiste',
'Mohammad Abdus Salam : physicien',
'Percy Lavon Julian : chimiste',
'Subrahmanyan Chandrasekhar : astrophysicien',
];
  1. Transformez les membres de people en un nouveau tableau de nœuds JSX, listItems :
const listItems = people.map(person => <li>{person}</li>);
  1. Renvoyez listItems à partir de votre composant, enrobé dans un <ul> :
return <ul>{listItems}</ul>;

Voici le résultat :

const people = [
  'Creola Katherine Johnson : mathématicienne',
  'Mario José Molina-Pasquel Henríquez : chimiste',
  'Mohammad Abdus Salam : physicien',
  'Percy Lavon Julian : chimiste',
  'Subrahmanyan Chandrasekhar : astrophysicien',
];

export default function List() {
  const listItems = people.map(person =>
    <li>{person}</li>
  );
  return <ul>{listItems}</ul>;
}

Error

Extra 185 of 186 byte(s) found at buffer[1]

Remarquez l’erreur dans la console du bac à sable :

Console
Warning: Each child in a list should have a unique “key” prop.

(« Avertissement : chaque enfant d’une liste devrait avoir une prop “key” unique », NdT.)

Vous apprendrez à corriger cette erreur plus loin dans cette page. Avant d’en arriver là, commençons par structurer un peu plus vos données.

Filtrer des tableaux d’éléments

On peut structurer davantage nos données.

const people = [{
id: 0,
name: 'Creola Katherine Johnson',
profession: 'mathématicienne',
}, {
id: 1,
name: 'Mario José Molina-Pasquel Henríquez',
profession: 'chimiste',
}, {
id: 2,
name: 'Mohammad Abdus Salam',
profession: 'physicien',
}, {
id: 3,
name: 'Percy Lavon Julian',
profession: 'chimiste',
}, {
id: 4,
name: 'Subrahmanyan Chandrasekhar',
profession: 'astrophysicien',
}];

Disons que vous cherchez un moyen de n’afficher que les chimistes. Vous pouvez utiliser la méthode filter() de JavaScript pour ne renvoyer que ces personnes. Cette méthode s’applique sur un tableau d’éléments, les fait passer à travers un « prédicat » (une fonction qui renvoie true ou false au sujet de son argument), et renvoie un nouveau tableau ne contenant que les éléments qui ont satisfait le prédicat (il a renvoyé true pour ces éléments).

Vous ne vous intéressez qu’aux éléments dont la profession est 'chimiste'. Le prédicat correspondant ressemble à (person) => person.profession === 'chimiste'. Voici comment assembler tout ça :

  1. Créez un nouveau tableau avec juste les chimistes, chemists, en appelant filter() sur people et en filtrant avec person.profession === 'chimiste' :
const chemists = people.filter(person =>
person.profession === 'chimiste'
);
  1. A présent transformez chemists avec map() :
const listItems = chemists.map(person =>
<li>
<img
src={getImageUrl(person)}
alt={person.name}
/>
<p>
<b>{person.name}:</b>
{' ' + person.profession + ' '}
célèbre pour {person.accomplishment}
</p>
</li>
);
  1. Enfin, renvoyez le listItems depuis votre composant :
return <ul>{listItems}</ul>;
import { people } from './data.js';
import { getImageUrl } from './utils.js';

export default function List() {
  const chemists = people.filter(person =>
    person.profession === 'chimiste'
  );
  const listItems = chemists.map(person =>
    <li>
      <img
        src={getImageUrl(person)}
        alt={person.name}
      />
      <p>
        <b>{person.name}:</b>
        {' ' + person.profession + ' '}
        célèbre pour {person.accomplishment}
      </p>
    </li>
  );
  return <ul>{listItems}</ul>;
}

Piège

Les fonctions fléchées renvoient implicitement l’expression qui suit immédiatement =>, de sorte que vous n’avez alors pas besoin d’une instruction return :

const listItems = chemists.map(person =>
<li>...</li> // return implicite !
);

En revanche, vous devez écrire un return explicite si votre => est suivie par une accolade { !

const listItems = chemists.map(person => { // Curly brace
return <li>...</li>;
});

Les fonctions fléchées utilisant => { ont un « corps de fonction ». Vous pouvez y écrire plus qu’une expression, mais vous devez écrire l’instruction return vous-même. Si vous l’oubliez, rien n’est renvoyé !

Maintenir l’ordre des éléments de liste avec key

Vous avez pu remarquer dans tous les bacs à sable ci-dessus une erreur dans la console :

Console
Warning: Each child in a list should have a unique “key” prop.

(« Avertissement : chaque enfant d’une liste devrait avoir une prop “key” unique », NdT.)

Vous devez fournir à chaque élément de tableau une key — une chaîne de caractères ou un nombre qui identifie de façon unique cet élément au sein du tableau :

<li key={person.id}>...</li>

Remarque

Les éléments JSX directement au sein d’un appel à map() ont toujours besoin de clés !

Les clés indiquent à React à quel élément du tableau de données correspond chaque élément du tableau de composants, pour qu’il puisse les faire correspondre plus tard. Ça devient important si les éléments de votre tableau sont susceptibles de s’y déplacer (par exemple dans le cadre d’un tri), d’y être insérés ou supprimés. Une key bien choisie aide React à inférer la nature exacte du changement, et à faire les mises à jour adaptées dans l’arbre DOM.

Plutôt que de générer vos clés à la volée, vous devriez les faire figurer dans vos données :

export const people = [{
  id: 0,
  name: 'Creola Katherine Johnson',
  profession: 'mathématicienne',
  accomplishment: 'ses calculs pour vol spatiaux',
  imageId: 'MK3eW3A'
}, {
  id: 1,
  name: 'Mario José Molina-Pasquel Henríquez',
  profession: 'chimiste',
  accomplishment: 'sa découverte du trou dans la couche d’ozone au-dessus de l’Arctique',
  imageId: 'mynHUSa'
}, {
  id: 2,
  name: 'Mohammad Abdus Salam',
  profession: 'physicien',
  accomplishment: 'sa théorie de l’électromagnétisme',
  imageId: 'bE7W1ji'
}, {
  id: 3,
  name: 'Percy Lavon Julian',
  profession: 'chimiste',
  accomplishment: 'ses travaux pionniers sur la cortisone, les stéroïdes et les pilules contraceptives',
  imageId: 'IOjWm71'
}, {
  id: 4,
  name: 'Subrahmanyan Chandrasekhar',
  profession: 'astrophysicien',
  accomplishment: 'son calcul de la masse des naines blanches',
  imageId: 'lrWQx8l'
}];

En détail

Afficher plusieurs nœuds DOM pour chaque élément de la liste

Que faire lorsque chaque élément de la liste doit produire non pas un, mais plusieurs nœuds DOM ?

La syntaxe concise de Fragment <>...</> ne vous permet pas de passer une clé, vous devez donc soit les regrouper dans une <div>, soit utiliser la syntaxe plus explicite <Fragment>, certes un peu plus longue, mais plus explicite :

import { Fragment } from 'react';

// ...

const listItems = people.map(person =>
<Fragment key={person.id}>
<h1>{person.name}</h1>
<p>{person.bio}</p>
</Fragment>
);

Les Fragments n’impactent pas le DOM, de sorte que ce code produira une liste à plat de <h1>, <p>, <h1>, <p>, et ainsi de suite.

Où récupérer la key

Selon la source de vos données, vous aurez différentes sources de clés :

  • Données issues d’une base de données : si vos données viennent d’une base de données, vous pouvez utiliser les clés / ID de la base, qui sont uniques par nature.
  • Données générées localement : si vos données sont générées et persistées localement (ex. une appli de prise de notes), utilisez un compteur incrémentiel, crypto.randomUUID() ou un module du style uuid en créant vos éléments.

Les règles des clés

  • Les clés doivent être uniques dans une même liste. En revanche, vous pouvez avoir les mêmes clés pour des nœuds JSX dans des tableaux distincts.
  • Les clés ne doivent pas changer sans quoi elles ne serviraient à rien ! Ne générez pas les clés lors du rendu.

Pourquoi React a-t-il besoin de clés ?

Imaginez que les fichiers sur votre bureau n’aient pas de noms. Vous y feriez alors référence plutôt par leur ordre : le premier fichier, le deuxième, et ainsi de suite. Vous pourriez vous y habituer, sauf que lorsque vous supprimez un fichier, un problème surgit. Le deuxième fichier devient le premier, le troisième devient le deuxième, etc.

Les noms de fichiers dans un dossier et les clés JSX dans un tableau jouent un rôle similaire. Ils nous permettent d’identifier de façon unique un élément parmi ceux qui l’entourent. Une clé bien choisie nous fournit plus d’information que la simple position dans le tableau. Même si la position change en raison d’un réordonnancement, la key permettra à React d’identifier l’élément tout au long de sa vie.

Piège

Vous pourriez être tenté·e d’utiliser la position d’un élément dans le tableau comme clé. C’est d’ailleurs ce que fera React si vous ne précisez pas de key. Mais l’ordre d’affichage des éléments variera au cours du temps si un élément est inséré, supprimé, ou si le tableau est réordonnancé. Utiliser l’index comme clé entraînera des bugs subtils et déroutants.

Dans le même esprit, évitez de générer les clés à la volée, du style key={Math.random()}. Les clés ne correspondraient alors jamais d’un rendu à l’autre, ce qui forcerait la recréation de vos composants et du DOM à chaque fois. C’est non seulement lent, mais ça entraînerait la perte des saisies dans les champs au sein des éléments de la liste. Utilisez plutôt un ID stable basé sur la donnée.

Notez que vos composants ne reçoivent pas la key dans leurs props. Elle n’est utilisée que comme indice par React lui-même. Si votre composant a besoin d’un ID, vous pouvez lui passer dans une prop dédiée : <Profile key={id} userId={id} />.

En résumé

Dans cette page, vous avez appris :

  • Comment extraire les données de vos composants pour les placer dans des structures de données comme des tableaux et des objets.
  • Comment générer des séries de composants similaires avec le map() de JavaScript.
  • Comment créer des tableaux filtrés d’éléments avec le filter() de JavaScript.
  • Pourquoi et comment utiliser key sur chaque composant d’une collection afin que React puisse garder trace de leurs identités même lorsque leurs positions ou les données sous-jacentes changent.

Défi 1 sur 4 ·
Découper une liste en deux

Cet exemple affiche une liste de personnes.

Modifiez-le pour afficher deux listes distinctes l’une de l’autre : les Chimistes et Tous les autres. Comme précédemment, vous pouvez déterminer si une personne est chimiste en testant person.profession === 'chimiste'.

import { people } from './data.js';
import { getImageUrl } from './utils.js';

export default function List() {
  const listItems = people.map(person =>
    <li key={person.id}>
      <img
        src={getImageUrl(person)}
        alt={person.name}
      />
      <p>
        <b>{person.name}:</b>
        {' ' + person.profession + ' '}
        célèbre pour {person.accomplishment}
      </p>
    </li>
  );
  return (
    <article>
      <h1>Scientifiques</h1>
      <ul>{listItems}</ul>
    </article>
  );
}