logo oujood
🔍

useReducer : Gérez des États Complexes de Manière Efficace

OUJOOD.COM

Introduction

Le Hook `useReducer` est utilisé pour gérer des états complexes dans React, notamment lorsqu'il y a plusieurs actions ou transformations possibles sur l'état. Contrairement à `useState`, qui est adapté aux états simples, `useReducer` permet de centraliser la logique d'état dans une fonction de réduction, rendant ainsi le code plus maintenable et scalable.

Dans ce chapitre, nous allons explorer :

  • Qu'est-ce que `useReducer` ?
  • Comment créer et utiliser un reducer.
  • Des exemples pratiques pour illustrer son fonctionnement.

1. Qu'est-ce que `useReducer` ?

`useReducer` est un Hook React qui prend deux arguments principaux :

  • Une fonction de réduction : Définit comment l'état doit être mis à jour en fonction des actions reçues.
  • Une valeur initiale : Représente l'état initial avant toute action.

Il retourne un tableau contenant deux éléments :

  • L'état actuel : Correspond à l'état géré par le reducer.
  • Une fonction dispatch : Permet d'envoyer des actions au reducer pour mettre à jour l'état.

2. Création d'un Reducer Simple

Voici un exemple simple où nous utilisons `useReducer` pour gérer un compteur avec plusieurs actions possibles :

📋 Copier le code

import React, { useReducer } from 'react';
import { createRoot } from 'react-dom/client';

// Fonction de réduction
function reducer(étatActuel, action) {
switch (action.type) {
case 'INCREMENTER':
return { ...étatActuel, compte: étatActuel.compte + 1 };
case 'DECREMENTER':
return { ...étatActuel, compte: étatActuel.compte - 1 };
case 'RESET':
return { ...étatActuel, compte: 0 };
default:
throw new Error('Action inconnue');
}
}

// Composant principal
function Compteur() {
const [état, dispatch] = useReducer(reducer, { compte: 0 }); // Initialisation de l'état

return (
<div>
<h1>Compteur : {état.compte}</h1>
<button onClick={() => dispatch({ type: 'INCREMENTER' })}>Incrémenter</button>
<button onClick={() => dispatch({ type: 'DECREMENTER' })}>Décrémenter</button>
<button onClick={() => dispatch({ type: 'RESET' })}>Réinitialiser</button>
</div>
);
}

const root = createRoot(document.getElementById('root'));
root.render(<Compteur />);

Explication détaillée ligne par ligne :

  1. import React, { useReducer } from 'react'; :
    • Nous importons React ainsi que le Hook useReducer.
    • useReducer est utilisé pour gérer des états complexes via une fonction de réduction.
  2. function reducer(étatActuel, action) { ... } :
    • Cette fonction de réduction définit comment l'état doit être mis à jour en fonction des actions reçues.
    • Elle prend deux arguments : étatActuel (l'état actuel) et action (l'action à exécuter).
  3. switch (action.type) { ... } :
    • Cette structure conditionnelle (switch) permet de gérer différentes actions possibles.
    • Ici, nous avons trois types d'actions : INCREMENTER, DECREMENTER, et RESET.
  4. case 'INCREMENTER': return { ...étatActuel, compte: étatActuel.compte + 1 }; :
    • Ce cas incrémente la valeur de l'état compte de 1.
    • { ...étatActuel } crée une copie de l'état actuel pour éviter de le modifier directement.
  5. case 'DECREMENTER': return { ...étatActuel, compte: étatActuel.compte - 1 }; :
    • Ce cas décrémente la valeur de l'état compte de 1.
  6. case 'RESET': return { ...étatActuel, compte: 0 }; :
    • Ce cas réinitialise la valeur de l'état compte à 0.
  7. const [état, dispatch] = useReducer(reducer, { compte: 0 }); :
    • Cette ligne initialise un état appelé état avec une valeur initiale { compte: 0 }.
    • dispatch est une fonction utilisée pour envoyer des actions au reducer.
  8. <button onClick={() => dispatch({ type: 'INCREMENTER' })}>Incrémenter</button> :
    • Ce bouton appelle la fonction dispatch avec une action { type: 'INCREMENTER' }.
    • Cela met à jour l'état via la fonction de réduction définie précédemment.
  9. const root = createRoot(document.getElementById('root')); :
    • Cette ligne crée une instance de racine React appelée root.
    • document.getElementById('root') spécifie l'élément HTML où le composant sera inséré.
  10. root.render(<Compteur />); :
    • Rend le composant Compteur dans l'élément HTML ayant l'id root.

3. Comparaison entre `useState` et `useReducer`

Bien qu'ils puissent sembler similaires, `useState` et `useReducer` ont des usages distincts. Voici leurs différences principales :

Aspect useState useReducer
Fonctionnement Gère un seul état simple et ses mises à jour. Gère des états complexes via une fonction de réduction centralisée.
Utilisation Idéal pour des états simples comme un compteur ou un texte. Parfait pour des états complexes impliquant plusieurs transformations ou actions.
Complexité Facile à utiliser mais limité pour des états avancés. Peut sembler plus complexe au départ, mais simplifie la gestion d'états complexes.

4. Exemple Avancé : Gestion d'un Panier d'Achat avec useReducer en React

Imaginons que vous souhaitez créer un panier d'achat avec des fonctionnalités comme l'ajout, la suppression et la mise à jour des produits. Voici comment cela peut être fait avec `useReducer` :

Explication du Code :

  • Utilisation de useReducer :
    • Permet de gérer un état complexe à travers une fonction de réduction.
    • Remplace useState pour des mises à jour plus structurées.
  • Fonction reducer :
    • Gère trois actions : ajouter un produit, supprimer un produit et vider le panier.
    • Met à jour l'état en fonction du type d'action reçu.
  • Actions disponibles :
    • AJOUTER_PRODUIT : Ajoute un produit au panier.
    • SUPPRIMER_PRODUIT : Supprime un produit du panier.
    • VIDER_PANIER : Vide complètement le panier.

📋 Copier le code

import React, { useReducer } from 'react';
import { createRoot } from 'react-dom/client';

// Fonction de réduction pour le panier
function reducer(étatActuel, action) {
switch (action.type) {
case 'AJOUTER_PRODUIT':
return {
	...étatActuel,
	produits: [...étatActuel.produits, action.payload]
};
case 'SUPPRIMER_PRODUIT':
return {
	...étatActuel,
	produits: étatActuel.produits.filter((_, index) => index !== action.payload)
};
case 'VIDER_PANIER':
return { ...étatActuel, produits: [] };
default:
throw new Error('Action inconnue');
}
}

// Composant principal
function Panier() {
const [état, dispatch] = useReducer(reducer, { produits: [] });

function ajouterProduit() {
dispatch({ type: 'AJOUTER_PRODUIT', payload: { nom: 'Nouveau produit', prix: 10 } });
}

function supprimerProduit(index) {
dispatch({ type: 'SUPPRIMER_PRODUIT', payload: index });
}

function viderPanier() {
dispatch({ type: 'VIDER_PANIER' });
}

return (
<div>
<h1>Panier d'achat</h1>
<button onClick={ajouterProduit}>Ajouter un produit</button>
<button onClick={viderPanier}>Vider le panier</button>
<ul>
	{état.produits.map((produit, index) => (
		<li key={index}>
			{produit.nom} - {produit.prix} €
			<button onClick={() => supprimerProduit(index)}>Supprimer</button>
		</li>
	))}
</ul>
</div>
);
}

const root = createRoot(document.getElementById('root'));
root.render(<Panier />);

Explication détaillée ligne par ligne :

  1. function reducer(étatActuel, action) { ... } :
    • Cette fonction de réduction gère les transformations de l'état du panier selon les actions reçues.
  2. case 'AJOUTER_PRODUIT': return { ...étatActuel, produits: [...étatActuel.produits, action.payload] }; :
    • Ce cas ajoute un nouveau produit au tableau produits de l'état.
    • action.payload représente le produit à ajouter.
  3. case 'SUPPRIMER_PRODUIT': return { ...étatActuel, produits: étatActuel.produits.filter((_, index) => index !== action.payload) }; :
    • Ce cas supprime un produit du tableau produits en filtrant l'élément correspondant à l'index spécifié dans action.payload.
  4. case 'VIDER_PANIER': return { ...étatActuel, produits: [] }; :
    • Ce cas réinitialise le tableau produits à un tableau vide.
  5. const [état, dispatch] = useReducer(reducer, { produits: [] }); :
    • Cette ligne initialise un état appelé état avec une valeur initiale contenant un tableau vide { produits: [] }.
    • dispatch est utilisée pour envoyer des actions au reducer.
  6. dispatch({ type: 'AJOUTER_PRODUIT', payload: { nom: 'Nouveau produit', prix: 10 } }); :
    • Cette ligne envoie une action AJOUTER_PRODUIT au reducer avec un objet produit comme payload.
  7. supprimerProduit(index) :
    • Cette fonction envoie une action SUPPRIMER_PRODUIT au reducer avec l'index du produit à supprimer.
  8. viderPanier() :
    • Cette fonction envoie une action VIDER_PANIER au reducer pour réinitialiser le panier.

Pourquoi utiliser useReducer ?

  • Facilite la gestion d'un état complexe.
  • Offre une structure claire et évolutive.
  • Idéal pour gérer des états qui impliquent plusieurs sous-actions.

Améliorations possibles :

  • Ajouter un champ pour personnaliser le nom et le prix des produits.
  • Afficher le total des prix dans le panier.
  • Persister les données dans localStorage.

Conclusion

Vous avez maintenant appris à utiliser le Hook `useReducer` pour gérer des états complexes dans vos applications React. En centralisant la logique d'état dans une fonction de réduction, vous pouvez rendre votre code plus maintenable et évolutif, surtout pour des cas d'utilisation avancés comme la gestion d'un panier d'achat.

Prochain chapitre : useCallback