Listes et Keys

Afficher des listes est une tâche quotidienne en React. Apprenons à le faire correctement avec .map() et les keys.

Afficher des listes avec .map()

En React, on utilise la méthode .map() pour transformer un tableau en éléments JSX.

Exemple simple

jsx
function UserList() {
  const users = ['Alice', 'Bob', 'Charlie'];

  return (
    <ul>
      {users.map(user => (
        <li>{user}</li>
      ))}
    </ul>
  );
}

Résultat :

• Alice
• Bob
• Charlie

Warning dans la console

Le code ci-dessus fonctionne mais génère un warning :

Warning: Each child in a list should have a unique "key" prop.

Pour résoudre cela, ajoutez une prop key unique à chaque élément.

La prop key - Pourquoi est-elle obligatoire ?

Les keys aident React à identifier quels éléments ont changé, été ajoutés ou supprimés.

Analogie : Liste de courses

Imaginez une liste de courses :

1. Pommes
2. Bananes
3. Oranges

Si vous ajoutez "Fraises" au milieu :

1. Pommes
2. Fraises    ← Nouvel élément
3. Bananes    ← A changé de position
4. Oranges    ← A changé de position

Sans keys : React pense que les éléments 2, 3 et 4 ont tous changé.

Avec keys : React sait qu'un seul élément a été ajouté, et réutilise les autres.

Comment React utilise les keys

Sans key :

jsx
<li>Alice</li>
<li>Bob</li>

React compare par position : "élément 1", "élément 2"

Avec key :

jsx
<li key="user-1">Alice</li>
<li key="user-2">Bob</li>

React compare par identité : "user-1", "user-2"

Résultat : Meilleures performances et moins de bugs !

Choisir la bonne key

Règle d'or

Utilisez une valeur unique et stable qui identifie chaque élément de façon permanente.

✅ Option 1 : ID unique (recommandé)

La meilleure solution : un identifiant unique fourni par vos données.

jsx
const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' }
];

return (
  <ul>
    {users.map(user => (
      <li key={user.id}>{user.name}</li>
    ))}
  </ul>
);

✅ Option 2 : Propriété unique

Si vos données n'ont pas d'ID, utilisez une autre propriété unique et stable.

jsx
const users = [
  { email: 'alice@example.com', name: 'Alice' },
  { email: 'bob@example.com', name: 'Bob' }
];

return (
  <ul>
    {users.map(user => (
      <li key={user.email}>{user.name}</li>
    ))}
  </ul>
);

❌ À éviter : L'index

N'utilisez jamais l'index si la liste peut changer (ajout, suppression, tri).

jsx
// ❌ Interdit pour les listes dynamiques
users.map((user, index) => (
  <li key={index}>{user.name}</li>
))

Pourquoi l'index pose problème

L'index change quand la liste est modifiée, ce qui trompe React :

jsx
// Avant : 3 utilisateurs
<li key={0}>Alice</li>
<li key={1}>Bob</li>
<li key={2}>Charlie</li>

// Après suppression de Bob
<li key={0}>Alice</li>   {/* ✅ Même key, même personne */}
<li key={1}>Charlie</li> {/* ❌ Même key, mais personne différente ! */}

Conséquences :

  • React pense que "Charlie" a remplacé "Bob" → re-render inutile
  • Si vos éléments ont un état (input, checkbox), l'état se retrouve sur le mauvais élément

Exception : Liste statique qui ne change jamais → index acceptable

Exemples pratiques

Exemple 1 : Liste de produits

jsx
function ProductList({ products }) {
  return (
    <div className="product-grid">
      {products.map(product => (
        <div key={product.id} className="product-card">
          <img src={product.image} alt={product.name} />
          <h3>{product.name}</h3>
          <p className="price">{product.price}</p>
          <button>Ajouter au panier</button>
        </div>
      ))}
    </div>
  );
}

// Utilisation
const products = [
  { id: 1, name: 'Laptop', price: 999, image: 'laptop.jpg' },
  { id: 2, name: 'Souris', price: 25, image: 'mouse.jpg' },
  { id: 3, name: 'Clavier', price: 75, image: 'keyboard.jpg' }
];

<ProductList products={products} />

Exemple 2 : Tableau de données

jsx
function UserTable({ users }) {
  return (
    <table>
      <thead>
        <tr>
          <th>Nom</th>
          <th>Email</th>
          <th>Rôle</th>
          <th>Actions</th>
        </tr>
      </thead>
      <tbody>
        {users.map(user => (
          <tr key={user.id}>
            <td>{user.name}</td>
            <td>{user.email}</td>
            <td>{user.role}</td>
            <td>
              <button>Éditer</button>
              <button>Supprimer</button>
            </td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

Filtrer avant d'afficher

Combinez .filter() et .map() pour afficher des listes filtrées.

Exemple : Filtrer par catégorie

jsx
function ProductList({ products, category }) {
  return (
    <div>
      {products
        .filter(product => product.category === category)
        .map(product => (
          <div key={product.id} className="product">
            <h3>{product.name}</h3>
            <p>{product.price}</p>
          </div>
        ))}
    </div>
  );
}

// Utilisation
const products = [
  { id: 1, name: 'Laptop', price: 999, category: 'electronics' },
  { id: 2, name: 'Shirt', price: 29, category: 'clothing' },
  { id: 3, name: 'Phone', price: 699, category: 'electronics' }
];

<ProductList products={products} category="electronics" />
// Affiche : Laptop, Phone

Exemple : Recherche

jsx
function SearchableList({ items, searchTerm }) {
  const filteredItems = items.filter(item =>
    item.name.toLowerCase().includes(searchTerm.toLowerCase())
  );

  return (
    <ul>
      {filteredItems.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

Trier avant d'afficher

Utilisez .sort() pour trier les données.

jsx
function ProductList({ products, sortBy = 'name' }) {
  // Créer une copie pour ne pas modifier l'original
  const sortedProducts = [...products].sort((a, b) => {
    if (sortBy === 'price') {
      return a.price - b.price;
    }
    return a.name.localeCompare(b.name);
  });

  return (
    <div>
      {sortedProducts.map(product => (
        <div key={product.id}>
          <h3>{product.name}</h3>
          <p>{product.price}</p>
        </div>
      ))}
    </div>
  );
}

Attention avec .sort()

.sort() modifie le tableau original. Créez une copie avant de trier :

jsx
// ❌ Modifie l'original
products.sort((a, b) => a.price - b.price)

// ✅ Crée une copie
[...products].sort((a, b) => a.price - b.price)

Gérer les listes vides

Affichez un message si la liste est vide.

Méthode 1 : Early return (recommandé)

jsx
function UserList({ users }) {
  if (users.length === 0) {
    return <p>Aucun utilisateur trouvé.</p>;
  }

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Méthode 2 : Opérateur &&

jsx
function UserList({ users }) {
  return (
    <div>
      {users.length === 0 && <p>Aucun utilisateur trouvé.</p>}

      {users.length > 0 && (
        <ul>
          {users.map(user => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
}

Listes imbriquées

Vous pouvez avoir des .map() à l'intérieur d'autres .map().

jsx
function CategoryList({ categories }) {
  return (
    <div>
      {categories.map(category => (
        <div key={category.id} className="category">
          <h2>{category.name}</h2>
          <ul>
            {category.items.map(item => (
              <li key={item.id}>{item.name}</li>
            ))}
          </ul>
        </div>
      ))}
    </div>
  );
}

// Données
const categories = [
  {
    id: 1,
    name: 'Électronique',
    items: [
      { id: 101, name: 'Laptop' },
      { id: 102, name: 'Phone' }
    ]
  },
  {
    id: 2,
    name: 'Vêtements',
    items: [
      { id: 201, name: 'T-shirt' },
      { id: 202, name: 'Pantalon' }
    ]
  }
];

Keys dans les listes imbriquées

Chaque niveau de .map() doit avoir ses propres keys :

jsx
{categories.map(category => (
  <div key={category.id}>       {/* Key du parent */}
    {category.items.map(item => (
      <div key={item.id}>       {/* Key de l'enfant */}
        {item.name}
      </div>
    ))}
  </div>
))}

Les keys doivent être uniques parmi les frères et sœurs, pas globalement.

Quelle est la meilleure key pour une liste d'utilisateurs provenant d'une API ?

Extraire en composant

Pour plus de lisibilité, extrayez les éléments de liste en composants.

Avant : Tout dans un composant

jsx
function ProductList({ products }) {
  return (
    <div>
      {products.map(product => (
        <div key={product.id} className="product">
          <img src={product.image} alt={product.name} />
          <h3>{product.name}</h3>
          <p>{product.price}</p>
          <button>Ajouter</button>
        </div>
      ))}
    </div>
  );
}

Après : Composant séparé

jsx
function ProductCard({ product }) {
  return (
    <div className="product">
      <img src={product.image} alt={product.name} />
      <h3>{product.name}</h3>
      <p>{product.price}</p>
      <button>Ajouter</button>
    </div>
  );
}

function ProductList({ products }) {
  return (
    <div>
      {products.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

Avantages

Lisibilité : Le code est plus clair et organisé

Réutilisabilité : ProductCard peut être utilisé ailleurs

Maintenabilité : Plus facile de modifier un composant isolé

Important : La key reste sur le composant dans le .map(), pas à l'intérieur !

Où doit se trouver la prop 'key' quand vous extrayez un élément de liste en composant ?

Récapitulatif

Ce que vous avez appris

Vous savez maintenant :

  • ✅ Utiliser .map() pour afficher des listes
  • ✅ Comprendre pourquoi les keys sont essentielles
  • ✅ Choisir de bonnes keys (ID unique > propriété unique > index)
  • ✅ Filtrer et trier des listes
  • ✅ Gérer les listes vides
  • ✅ Créer des listes imbriquées
  • ✅ Extraire des éléments en composants

Prochaine étape : Afficher du contenu conditionnel !