
Mise en place de `useReducer` (état initial, fonction reducer, dispatch)
Apprenez étape par étape comment configurer et utiliser le hook useReducer de React : définir l'état initial, écrire la fonction reducer et utiliser dispatch.
Introduction : Les trois piliers de `useReducer`
Pour utiliser efficacement le hook `useReducer` dans vos composants fonctionnels React, vous devez comprendre et mettre en place trois éléments essentiels : l'état initial qui définit le point de départ, la fonction reducer qui encapsule toute la logique de mise à jour, et l'appel au hook lui-même qui vous donne accès à l'état actuel et à la fonction `dispatch` pour déclencher les changements.
Cette section vous guidera à travers chacune de ces étapes, en illustrant le processus avec un exemple concret pour clarifier comment ces pièces s'assemblent pour gérer l'état de manière structurée.
Etape 1 : Définir l'Etat Initial (`initialState`)
Tout comme avec `useState`, vous devez fournir une valeur initiale pour l'état géré par `useReducer`. Cette valeur, souvent appelée initialState, représente l'état de votre composant lors de son premier rendu, avant qu'aucune action n'ait été dispatchée.
L'état initial peut être une valeur simple (nombre, chaîne, booléen) ou, plus fréquemment avec `useReducer`, un objet contenant plusieurs propriétés liées.
// Exemple 1: Etat initial simple pour un compteur
const initialCounterState = { count: 0 };
// Exemple 2: Etat initial plus complexe pour un fetch de données
const initialFetchState = {
data: null,
isLoading: false,
error: null,
};
Initialisation Paresseuse (Lazy Initialization)
Similaire à `useState`, `useReducer` permet également une initialisation paresseuse. Au lieu de passer directement l'état initial, vous pouvez passer une fonction (appelée `init` function). Cette fonction ne sera exécutée qu'une seule fois au montage pour calculer l'état initial. C'est utile si le calcul de l'état initial est coûteux. Le hook `useReducer` accepte alors trois arguments : `reducer`, `initialArg`, et `init`.
function calculateInitialState(initialValue) {
console.log("Calcul initial coûteux...");
// ... logique complexe ...
return { value: initialValue * 2 };
}
// Dans le composant:
// const [state, dispatch] = useReducer(myReducer, props.initialValue, calculateInitialState);
// 'calculateInitialState' sera appelée avec 'props.initialValue' une seule fois.
Etape 2 : Définir la Fonction Reducer (`reducer`)
C'est le coeur de la logique de `useReducer`. La fonction reducer détermine comment l'état change en réponse à une action. Rappelez-vous, elle doit être une fonction pure qui prend deux arguments :
state: L'état actuel.action: L'objet action qui a été dispatché (contenant généralement untypeet parfois unpayload).
Elle doit retourner le nouvel état (sans muter l'ancien état).
// Exemple de reducer pour gérer l'état du fetch de données
function fetchReducer(state, action) {
switch (action.type) {
case 'FETCH_START':
// Retourne un nouvel objet état
return { ...state, isLoading: true, error: null };
case 'FETCH_SUCCESS':
return { ...state, isLoading: false, data: action.payload, error: null };
case 'FETCH_ERROR':
return { ...state, isLoading: false, error: action.payload };
case 'RESET':
// Peut utiliser l'état initial (si disponible dans la portée) ou une version définie ici
return { data: null, isLoading: false, error: null };
default:
// Important: retourner l'état inchangé pour les actions non reconnues
return state;
}
}
La structure switch sur action.type est très courante car elle permet de gérer clairement les différentes transitions d'état possibles.
Etape 3 : Appeler `useReducer` et obtenir `state` et `dispatch`
Maintenant, à l'intérieur de votre composant fonctionnel, vous appelez le hook `useReducer` en lui passant votre fonction reducer et votre état initial (ou les arguments pour l'initialisation paresseuse).
Le hook retourne un tableau contenant deux éléments, que vous déstructurez généralement :
state: La valeur actuelle de l'état (initialement,initialState). Cette variable contient la dernière valeur retournée par votre reducer.dispatch: La fonction spéciale que vous utiliserez pour envoyer (dispatcher) des actions à votre reducer afin de déclencher des mises à jour d'état. React garantit que la fonction `dispatch` a une identité stable et ne changera pas entre les rendus.
import React, { useReducer, useEffect } from 'react';
// Supposons que initialFetchState et fetchReducer sont définis comme ci-dessus
function DataComponent() {
// Appel du hook useReducer
const [state, dispatch] = useReducer(fetchReducer, initialFetchState);
// Utilise state pour afficher l'UI
const { data, isLoading, error } = state;
useEffect(() => {
// Utilise dispatch pour signaler le début du fetch
dispatch({ type: 'FETCH_START' });
fetch('/api/mydata')
.then(response => {
if (!response.ok) throw new Error('Erreur réseau');
return response.json();
})
.then(fetchedData => {
// Utilise dispatch pour signaler le succès
dispatch({ type: 'FETCH_SUCCESS', payload: fetchedData });
})
.catch(fetchError => {
// Utilise dispatch pour signaler l'erreur
dispatch({ type: 'FETCH_ERROR', payload: fetchError.message });
});
}, []); // Exécute une seule fois au montage
if (isLoading) {
return Chargement...;
}
if (error) {
return Erreur : {error};
}
return (
Données :
{JSON.stringify(data, null, 2)} );}export default DataComponent;Etape 4 : Utiliser `dispatch`
La fonction `dispatch` est le seul moyen d'initier une mise à jour de l'état géré par `useReducer`. Vous l'appelez en lui passant un objet action. Cet objet action est ensuite transmis comme second argument à votre fonction reducer.
Comme vu dans l'exemple précédent, vous appelez `dispatch` depuis vos gestionnaires d'événements (onClick sur le bouton Reset) ou depuis des `useEffect` (pour gérer les différentes étapes d'un fetch asynchrone) en fournissant l'objet action approprié ({ type: 'RESET' }, { type: 'FETCH_START' }, { type: 'FETCH_SUCCESS', payload: data }, etc.).
Conclusion : Un flux structuré pour l'état
La mise en place de `useReducer` implique de définir clairement votre état initial, d'écrire une fonction reducer pure et immuable qui gère toutes les transitions d'état, et d'utiliser le hook pour obtenir l'état actuel et la fonction `dispatch`. En appelant `dispatch` avec des actions descriptives, vous déclenchez l'exécution du reducer qui calcule le nouvel état, assurant un flux de données prévisible et centralisé.
Ce processus, bien que légèrement plus verbeux que `useState` au départ, offre une structure et une clarté précieuses pour gérer les logiques d'état complexes, facilitant la maintenance, le débogage et les tests.