Retour au cours

frontend / react

Listes et clés

Leçon 51 exercice

Explication

Ce que vous allez apprendre

  • Transformer un tableau de données en éléments JSX avec .map()
  • Comprendre le rôle exact de la prop key dans l'algorithme de réconciliation
  • Identifier pourquoi utiliser l'index du tableau comme key est risqué
  • Choisir un identifiant stable et unique pour chaque élément d'une liste
  • Gérer correctement les clés dans des listes imbriquées ou avec des fragments

Dans quel contexte ?

Dans src/components/TodoList.jsx, un utilisateur coche une tâche en haut de sa liste, mais c'est une autre tâche, plus bas, qui se retrouve visuellement cochée. Le composant utilisait key={index} : dès qu'une tâche est supprimée ou réordonnée, React réutilise les mêmes nœuds DOM par position et associe le mauvais état visuel au mauvais élément. Remplacer key={index} par key={tache.id} corrige immédiatement le bug.

Étape 1 : un besoin très fréquent

Afficher une collection de données (produits, utilisateurs, messages) est l'un des besoins les plus fréquents. En React, cela se fait généralement avec .map(), qui transforme un tableau de données en tableau d'éléments JSX.

Étape 2 : le problème quand la liste change

Quand cette liste change dans le temps, un élément ajouté ou supprimé, React a besoin d'un moyen de savoir PRÉCISÉMENT quel élément affiché correspond à quelle donnée d'un rendu à l'autre.

Étape 3 : la solution, la prop key

key est une prop spéciale, réservée par React, qui sert d'identifiant stable pour chaque élément d'une liste. Elle n'est jamais accessible à l'intérieur du composant enfant : c'est une métadonnée interne.

Étape 4 : le réflexe naturel, et pourquoi il est risqué

Le réflexe naturel du débutant est d'utiliser l'index du tableau comme clé, parce qu'il est toujours disponible. Le problème apparaît dès que la liste change d'ordre ou qu'un élément est inséré ailleurs qu'en toute fin.

Étape 5 : ce que ce piège provoque concrètement

React associe alors le mauvais contenu au mauvais élément DOM, ce qui peut provoquer des bugs visuels subtils, voire des pertes de state internes comme un champ de saisie affichant soudain la mauvaise valeur.

Piège fréquent

key={index} semble fonctionner tant que la liste ne fait que s'allonger en fin de tableau. Dès qu'un élément est inséré au milieu, supprimé, ou que la liste est triée, React réattribue les nœuds DOM par position et mélange les states internes (champs de saisie, cases cochées).

Étape 6 : la bonne pratique

La bonne pratique est d'utiliser un identifiant métier stable et unique, comme un id venant de la base de données, qui reste attaché à la même donnée quelle que soit sa position.

Source de la cléStable si la liste est réordonnée ?Recommandé
id de la base de donnéesOuiOui, toujours en priorité
UUID généré à la création de l'itemOuiOui
Index du tableau (index)NonSeulement si la liste est strictement statique
Contenu de l'item (ex: le texte)Non, si doublons possiblesNon

Et ensuite ?

Cette leçon prolonge les notions de composants et d'événements déjà vues. La prochaine étape aborde un sujet essentiel : comment sortir du rendu pur pour interagir avec le monde extérieur, avec useEffect.

Commandes & code

Listes et clés

key est l'identifiant que React utilise pour suivre chaque élément d'une liste entre deux rendus.

jsx
function ListeUtilisateurs({ utilisateurs }) {
    return (
        <ul>
            {utilisateurs.map((u) => (
                <li key={u.id}>{u.nom}</li>
                // key DOIT être stable et unique DANS la fratrie -- l'id métier, pas l'index
            ))}
        </ul>
    );
}
jsx
// Pourquoi l'index comme key est dangereux : illustration du bug
function ListeAvecIndex({ items }) {
    return (
        <ul>
            {items.map((item, index) => (
                <li key={index}>
                    {/* si on insère un item au DÉBUT du tableau, React réutilise les DOM nodes
                        par index -> chaque <li> "hérite" du contenu du précédent, y compris
                        les states internes (ex: un input non contrôlé) -> bugs visuels/de state */}
                    <input defaultValue={item.nom} />
                </li>
            ))}
        </ul>
    );
}

// CORRECT : un identifiant stable, indépendant de la position
function ListeAvecId({ items }) {
    return (
        <ul>
            {items.map((item) => (
                <li key={item.id}>
                    <input defaultValue={item.nom} />
                </li>
            ))}
        </ul>
    );
}
// index acceptable UNIQUEMENT si la liste est strictement statique
// (jamais réordonnée, jamais filtrée, jamais d'insertion/suppression)
jsx
// Listes imbriquées : chaque niveau de map a besoin de sa propre key
function TableauProduits({ categories }) {
    return (
        <div>
            {categories.map((cat) => (
                <section key={cat.id}>
                    <h2>{cat.nom}</h2>
                    <ul>
                        {cat.produits.map((p) => (
                            <li key={p.id}>{p.nom} - {p.prix}€</li>
                        ))}
                    </ul>
                </section>
            ))}
        </div>
    );
}
jsx
// Fragment avec key : quand un item de liste doit rendre plusieurs éléments frères
function ListeDefinitions({ termes }) {
    return (
        <dl>
            {termes.map((t) => (
                <Fragment key={t.id}>
                    <dt>{t.mot}</dt>
                    <dd>{t.definition}</dd>
                </Fragment>
                // <> </> raccourci ne permet PAS de passer key -- Fragment explicite obligatoire ici
            ))}
        </dl>
    );
}

Résumé

  • key doit être un identifiant stable et unique dans la fratrie, jamais recalculé à chaque rendu.
  • L'index comme key casse dès que la liste est réordonnée, filtrée ou modifiée en tête.
  • key n'est jamais accessible en tant que prop dans le composant enfant : c'est une métadonnée interne à React.
  • <Fragment key={...}> explicite est nécessaire dès qu'on a besoin d'une key sur un fragment.

Exercices pratiques

1 disponible
1

Mission : traquer le bug de la case cochée fantôme

Objectif : Diagnostiquer un bug d'attribution de state causé par key={index} et migrer vers une clé stable.

Contexte

Sur TodoList.jsx, un utilisateur coche la première tâche de sa liste, mais visuellement c'est une tâche plus bas qui apparaît cochée. Le composant utilise {taches.map((tache, index) => <li key={index}><input type="checkbox" defaultChecked={tache.faite} />{tache.texte}</li>)}. Une insertion de nouvelle tâche en tête de liste précède toujours ce genre de bug dans les tickets remontés par les utilisateurs.

Résoudre l’exercice →