Rendu de listes & États d'UI

.map(), keys, filtrage, tri et discriminated unions

Utilisez les flèches, cliquez ou glissez pour naviguer

Objectifs de la leçon

1. Rendre des listes avec .map()

Générer du JSX dynamique

2. Comprendre les keys

Pourquoi React en a besoin

3. Filtrer et trier

De façon immutable

4. Gérer les états async

loading, error, empty, success

5. Discriminated unions

Typer les états d'UI

6. Patterns conditionnels

Affichage selon l'état

Plan du cours

1

Rappel : .map() en JavaScript

Vous connaissez déjà du backend

2

.map() génère du JSX dynamique

La base du rendu de listes

3

Les keys : pourquoi et comment

Pourquoi index est un mauvais choix

4

Demo live : filtrage + tri

Avec useState

5

Discriminated unions pour les états

Lien avec DDD

Rappel : .map() en JavaScript

Vous connaissez déjà .map() du backend!

// JavaScript classique

const numbers = [1, 2, 3];

const doubled = numbers.map(n => n * 2);

// [2, 4, 6]

.map() transforme chaque élément et retourne un nouveau tableau

.map() génère du JSX

En React, .map() retourne des éléments JSX!

function UserList() {

const users = [

{ id: 1, name: "Alice" },

{ id: 2, name: "Bob" },

];

return (

<ul>

{users.map(user => (

<li key={user.id}>{user.name}</li>

))}

</ul>

);

}

Les accolades {} permettent d'insérer des expressions JavaScript dans JSX

Pourquoi les keys ?

React a besoin d'identifier chaque élément

Les keys aident React à réconcilier le DOM

Quand la liste change, React sait quoi mettre à jour

Comment choisir une key ?

Bonnes keys

  • Unique et stable
  • Prévisible entre les renders
  • Ex: user.id, product.sku

Mauvaises keys

  • Index du tableau
  • Math.random()
  • Date.now()

// Bon - ID unique de la DB

<li key={user.id}>{user.name}</li>

// Mauvais - index du tableau

<li key={index}>{user.name}</li>

Pourquoi index est dangereux ?

L'index change quand la liste est réordonnée ou filtrée!

Liste originale

[A, B, C]

keys: 0, 1, 2

=>

Après filtre (supprime A)

[B, C]

keys: 0, 1

React pense que B est devenu A (key 0), C est devenu B (key 1)...

Le state local des composants peut être perdu!

Oublier la key

React affiche un warning dans la console!

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

Sans key

  • Warning dans la console
  • Performances dégradées
  • Bugs potentiels

Avec key

  • Pas de warning
  • Réconciliation optimisée
  • State préservé

Filtrage immutable

filter() avant .map()

filter() crée un nouveau tableau

On ne mute jamais le tableau original!

Exemple : Filtrer une liste

function ProductList() {

const [products, setProducts] = useState([...]);

const [search, setSearch] = useState("");

const filtered = products.filter(p =>

p.name.toLowerCase().includes(search.toLowerCase())

);

return (

<>

<input value={search} onChange={e => setSearch(e.target.value)} />

<ul>{filtered.map(p => <li key={p.id}>{p.name}</li>)}</ul>

</>

);

}

filter() ne modifie pas products, il crée un nouveau tableau!

Tri immutable : Attention!

sort() modifie le tableau EN PLACE!

Mauvais

products.sort((a, b) => a.price - b.price);

Mute le tableau original!

Bon

[...products].sort((a, b) => a.price - b.price);

Crée une copie, puis trie

Toujours copier avant sort() : [...array].sort()

Chaînage : filter + sort + map

const sorted = [...products]

.filter(p => p.inStock)

.sort((a, b) => a.price - b.price)

.map(p => <ProductCard key={p.id} product={p} />);

1. filter

Garde les éléments

2. sort

Ordonne

3. map

Transforme en JSX

Chaque étape crée un nouveau tableau - jamais de mutation!

Les 3 états d'une donnée async

Toujours les gérer TOUS!

loading

Donnée en cours de chargement

error

Erreur lors du chargement

success

Donnée disponible

L'état vide : souvent oublié!

Une liste filtrée peut retourner 0 résultat...

Problème

L'utilisateur ne voit rien

Est-ce un bug? Un chargement?

Solution

Afficher un message explicite

"Aucun résultat trouvé"

if (filtered.length === 0) {

return <p>Aucun produit trouvé</p>;

}

Pattern naïf : booléens séparés

Plusieurs états qui peuvent se chevaucher...

const [isLoading, setIsLoading] = useState(true);

const [error, setError] = useState(null);

const [data, setData] = useState(null);

Problème : On peut être loading ET error en même temps!

Comment afficher le bon UI?

Discriminated Unions

Typer les états d'UI avec TypeScript

Un type qui rend les états mutuellement exclusifs

Impossible d'être loading ET error!

Définition du type

type AsyncState<T> =

| { status: "loading" }

| { status: "error"; message: string }

| { status: "success"; data: T };

La propriété status est le discriminant

TypeScript sait quelles propriétés sont disponibles selon la valeur de status

Utilisation avec useState

const [state, setState] = useState<AsyncState<User[]>>({

status: "loading"

});

// Plus tard...

setState({ status: "success", data: users });

setState({ status: "error", message: "Échec du chargement" });

Un seul state, un seul type - impossible d'avoir des états incohérents!

Rendu conditionnel avec switch

function UserList({ state }: { state: AsyncState<User[]> }) {

switch (state.status) {

case "loading":

return <Spinner />;

case "error":

return <ErrorMessage>{state.message}</ErrorMessage>;

case "success":

return <ul>{state.data.map(u => <li key={u.id}>{u.name}</li>)}</ul>;

}

}

TypeScript sait que state.data existe seulement quand status === "success"!

Lien avec DDD

Vous connaissez peut-être ce pattern du backend...

DDD : Result Type

Result<T, E>

Success(T) ou Error(E)

États mutuellement exclusifs

Discriminated Union

AsyncState<T>

loading | error | success

Même concept appliqué au frontend

Les discriminated unions apportent la rigueur du backend dans vos composants

Pièges courants

Les erreurs à éviter

Ces erreurs sont fréquentes et compréhensibles

Piège 1 : Index comme key

L'erreur

{items.map((item, index) =>

<li key={index}>{item.name}</li>

)}

L'index change quand la liste change!

La solution

{items.map(item =>

<li key={item.id}>{item.name}</li>

)}

Utilisez un ID unique et stable

L'index est acceptable SEULEMENT si la liste est statique et ne change jamais

Piège 2 : Oublier la key

React affiche un warning ET les performances se dégradent

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

Check the render method of `UserList`.

Sans key

React doit recréer tous les éléments

O(n) comparaisons

Avec key

React peut réutiliser les éléments

O(1) pour les éléments inchangés

Piège 3 : sort() mute le tableau!

sort() modifie le tableau EN PLACE - c'est rare en JS!

Mauvais

const sorted = products.sort(...);

products est muté!

Bon

const sorted = [...products].sort(...);

Copie explicite avant sort

// Méthodes qui MUTENT en JS :

push(), pop(), shift(), unshift()

splice(), sort(), reverse()

// Méthodes qui NE MUTENT PAS :

map(), filter(), reduce(), slice(), concat()

Piège 4 : Ignorer l'état vide

Une liste filtrée peut retourner 0 résultat...

Problème

{filtered.map(item => ...)}

Écran vide si filtered = []

L'utilisateur ne comprend pas

Solution

{filtered.length === 0 ? (

<p>Aucun résultat</p>

) : (

filtered.map(...)

)}

Message explicite

Points clés à retenir

Chaque élément DOIT avoir une key unique et stable

Pas l'index! Utilisez un ID de la DB

filter() et sort() avant .map()

Ne pas oublier [...array].sort()

Les 3 états : loading, error, success

Toujours les gérer TOUS

Discriminated unions = états mutuellement exclusifs

Impossible d'être loading ET error

À retenir!

.map() + key

{items.map(i => <li key={i.id}>...</li>)}

Key unique et stable

Immutabilité

[...items].sort() avant .map()

Jamais de mutation

États async

loading | error | success

+ empty state!

Discriminated Union

status: "loading" | "error" | "success"

TypeScript protège

Les listes sont au coeur de la plupart des interfaces!

Exercices pratiques

1. Liste de produits avec recherche

filter() + .map() avec keys correctes

2. Tri par prix (croissant/décroissant)

[...products].sort() sans mutation

3. Composant AsyncList avec discriminated union

Gérer loading, error, success, empty

4. Liste filtrable + triable + message vide

Combiner tous les concepts

Questions?

Les listes et les états sont partout en React

Prochaine étape : useEffect et les effets de bord