frontend / react
Listes et clés
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
keydans l'algorithme de réconciliation - Identifier pourquoi utiliser l'index du tableau comme
keyest 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ées | Oui | Oui, toujours en priorité |
| UUID généré à la création de l'item | Oui | Oui |
Index du tableau (index) | Non | Seulement si la liste est strictement statique |
| Contenu de l'item (ex: le texte) | Non, si doublons possibles | Non |
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.
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>
);
}// 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)// 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>
);
}// 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é
keydoit être un identifiant stable et unique dans la fratrie, jamais recalculé à chaque rendu.- L'index comme
keycasse dès que la liste est réordonnée, filtrée ou modifiée en tête. keyn'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
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.