Utiliser useReducer plutôt que useState
A React‘s Journey ⚛️
Le framework React (que dis-je, la librairie 😉) utilise des états réactifs au sein de ses composants pour opérer les changements dans le DOM (au travers de son Virtual DOM). Pour la plupart d’entre vous qui ont déjà joué avec React (et au fameux counter), vous saurez qu’on peut déclarer un état de composant immutable via le hook useState ; pour les nouveaux, voici un exemple concret :
import { useState } from 'react';
export function App() {
const [counter, setCounter] = useState(0);
const increment = () => setCounter(value => value += 1);
const decrement = () => setCounter(value => value -= 1);
return (
<div className="flex-col">
<span>{counter}</span>
<div className="flex-row">
<button onClick={increment}>-1</button>
<button onClick={decrement}>+1</button>
</div>
</div>
);
}
Pour les états « simples », je préconise d’utiliser cette manière de faire. En revanche, pour les données plus complexes (les objets et / ou les objets imbriqués, etc…), il est préférable d’utiliser useReducer !
useReducer, Comment Ça Marche ?
Réutilisons l’exemple du compteur ci-dessus, en changeant la manière de gérer l’état :
import { useReducer } from 'react';
const counterReducer = (state, action) => {
switch (action.type) {
case 'increment':
return state + 1;
case 'decrement':
return state - 1
default:
return state;
}
};
export function App() {
const [counterState, dispatch] = useReducer(0, counterReducer);
const increment = () => dispatch({ type: 'increment' });
const decrement = () => dispatch({ type: 'decrement' });
return (
<div className="flex-col">
<span>{counterState}</span>
<div className="flex-row">
<button onClick={increment}>-1</button>
<button onClick={decrement}>+1</button>
</div>
</div>
);
}
Ok, pour une donnée « simple » (boolean, number, string) ce n’est peut-être pas la peine… Pour autant, comment ça marche ? Pour ceux qui connaissent Redux et la notion de « reducer », cette manière de déclarer une variable réactive repose sur le pattern « reducer » : une fonction switch / case qui prend une valeur en premier argument, et qui retourne cette même valeur « réduite » d’après les données du second argument.
Le hook useReducer retourne l’état immuable (comme useState), puis une fonction dispatch en tant que deuxième paramètre pour mettre à jour cet état. La fonction dispatch permet de diffuser une « action« , composée d’un « type« » et parfois d’un « payload » (par convention). Lorsque l’action est « dispatchée« , l’algorithme switch / case est interrogé et va « réduire » l’état initial.
Voici un autre exemple dans le cadre d’un formulaire, et donc d’une donnée plus complexe :
import { useReducer, useEffect } from 'react';
const initialState = {
firstName: '',
lastName: ''
};
const formReducer = (state, action) => {
switch (action.type) {
case 'setFirstName':
return {
...state,
firstName: action.payload
};
case 'setLastName':
return {
...state,
lastName: action.payload
};
default:
return {
...state,
[payload.key]: payload.value
};
}
};
export function App() {
const [formState, dispatch] = useReducer(initialState, formReducer);
useEffect(() => {
console.log('formState', formState);
}, [formState]);
const setFirstName = (event) => dispatch({ type: 'setFirstName', payload: event.target.value });
const setLastName = (event) => dispatch({ type: 'setLastName', payload: event.target.value });
const setEmail = (event) => dispatch({ type: 'setEmail', payload: { key: 'email', value: event.target.value } });
return (
<form className="flex-col">
<div className="flex-col">
<label htmlFor="firstName">FirstName</label>
<input id="firstName" onChange={setFirstName} placeholder="John" />
</div>
<div className="flex-col">
<label htmlFor="lastName">LastName</label>
<input id="lastName" onChange={setlastName} placeholder="Doe" />
</div>
<div className="flex-col">
<label htmlFor="email">Email</label>
<input id="email" onChange={setEmail} type="email" />
</div>
</form>
);
}
D’après le code ci-dessus, les deux actions setFirstName et setLastName pointeront vers les références (« type« ) du même libellé depuis le « reducer« . La dernière fonction setEmail va atterrir dans le default (puisque l’action n’a pas de référence dans le switch / case) et ajouter une propriété ainsi qu’une valeur à l’état du formulaire (state). Ainsi avec les valeurs John, Doe et john.doe@gmail.com, on devrait se retrouver le résultat suivant :
{
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@gmail.com"
}
Et voilà ! Il n’y a rien de plus à savoir sur l’usage du hook useReducer plutôt que useState. De manière objective, l’usage d’un algorithme “reducer“ (switch / case) apporte une certaine rigueur / une meilleure robustesse à votre code, car la logique métier est davantage structurée (et donc maintenable). La puissance de ce hook est telle qu’il permet également de déclarer une mécanique de State Management (couplé à l’API Context de React), mais ceci est une autre histoire…
Développeur FullStack passionné par l’informatique depuis plus d’une décennie…
J’ai commencé à développer des applications mobiles en Java puis en Kotlin pour Android. En découvrant NodeJS et le framework AngularJS (paix à son âme), je me suis trouvé une véritable passion pour le développement Web, mais surtout pour le langage JavaScript.
React, Vue, TypeScript, MongoDB, GraphQL, (S)CSS, etc… sont les technologies que j’apprécie de manipuler au quotidien. Aujourd’hui, je conçois et maintiens des applications et sites Web, de A à Z (depuis les APIs jusqu’au rendu visuel). J’accorde également une attention particulière à l’UI/UX, que je considère comme la pierre angulaire d’un projet réussi !
L’art du développement et la curiosité pour la nouveauté, me poussent à partager mes connaissances avec mes pairs, au travers d’articles, de conférences ou encore la formation.
Conscient de l’aspect chronophage du code, j’aspire à l’avenir à gérer des projets informatiques de toutes tailles. En attendant, j’avance dans la vie la tête pleine d’idées nouvelles, le regard tourné vers le Web !