Compare commits

..

No commits in common. "8ad370940f4a483287ec7f1b5ddd6f3589182815" and "b6046cd77f9959edad3e58abacab47f388771aa1" have entirely different histories.

8 changed files with 1 additions and 1453 deletions

View File

@ -32,7 +32,7 @@ export function QuestionIcon(props: GradientProps) {
stroke-linejoin="round" stroke-linejoin="round"
/> />
<path <path
d="M 15.613 15.7139 C 15.8705 15.1461 17.758 11.3442 17.8109 10.4986 C 17.9128 8.8632 15.5223 8.1803 14.6161 9.3819 C 14.4617 9.5862 13.8186 9.7889 13.8694 9.2081 C 14.0808 7.755 16.4352 7.3025 17.9493 8.5954 C 18.5705 9.1256 18.8798 9.9497 18.7641 10.6412 C 18.5471 11.9402 16.7786 15.9192 16.7786 15.9192 C 16.2095 17.065 15.3394 16.3173 15.613 15.7139 Z" d="m 16.39 14.617 l 1.179 -3.999 C 17.38 9.304 16.133 9.127 15.469 10.645 C 15.306 11.269 14.71 11.12 14.71 10.537 a 1.66 1.66 5 1 1 3.808 0.217 l -1.5182 5.4314 a 0.602 0.602 5 0 1 -1.1795 -0.1032 Z"
class="fill-[var(--icon-foreground)] stroke-[color:var(--icon-foreground)]" class="fill-[var(--icon-foreground)] stroke-[color:var(--icon-foreground)]"
stroke-width={2} stroke-width={2}
stroke-linecap="round" stroke-linecap="round"

View File

@ -1,167 +0,0 @@
---
title: Les hooks de React
description: Découvre les hooks de React, une fonctionnalité qui te permet de gérer le state et le cycle de vie de tes composants fonctionnels.
tags: [Frontend, React, JavaScript, TypeScript, Bibliothèque, Interface utilisateur (UI)]
---
import Callout from "@/components/Callout";
import tabs from "./tabs";
Ça y est, on rentre dans le vif du sujet avec les **hooks** de React !
On en a déjà parlé un peu dans l'article précédent _(notamment avec le hook `useState` pour déclarer un state)_, mais on va maintenant les aborder en détail.
## 🎣 Qu'est-ce qu'un hook ?
Tu te souviens du charabia dans l'article précédent ?
> Un **hook** en React est une fonction qui permet d'exploiter les fonctionnalités de React dans un composant fonctionnel _(fonction)_.
Essayons de comprendre cette phrase un peu plus en détail en prenant l'origine des composants React.
Historiquement, on utilisait des **classes** pour déclarer des composants React plutôt que des **fonctions**.
C'était pas mal, mais ça devenait vite compliqué à gérer, notamment pour partager de la logique entre plusieurs composants _(comme le state par exemple)_.
Pour te donner un aperçu, voici à quoi ressemblait un composant de classe avec les trois étapes du cycle de vie :
<tabs.reactClassComponent />
Comme dirait l'un de mes chers confrères jury :
> C'est pas téros 😕
Mais si tu as bien fait attention, tu as pu remarquer trois méthodes _(on ne prend pas le constructor en compte)_ qui sont appelées à des moments précis du cycle de vie du composant :
- `componentDidMount` : appelée après le premier rendu du composant
- `componentDidUpdate` : appelée après chaque mise à jour du composant
- `componentWillUnmount` : appelée avant la suppression du composant
Seulement, comment on peut faire pour gérer ces étapes avec des composants fonctionnels ?
Et bien c'est là qu'interviennent les **hooks** !
Avant de te montrer **comment** reproduire ces étapes avec des **hooks**, voici les **principaux hooks** de base que tu vas **très souvent** utiliser :
- `useState` : pour déclarer un **state**
- `useEffect` : pour gérer le **cycle de vie** d'un composant _(on en parle juste après !)_
Il en existe d'autres bien entendu, mais ces deux-là sont les plus utilisés.
On reviendra sur les autres hooks "basiques" un peu plus tard 😉
## useEffect, ou la machinerie du cycle de vie
Ce hook.. il est **tellement** puissant !
Mais il est surtout très mal compris par les débutants _(et même parfois par les confirmés)_.
Il faut dire que Facebook n'a pas aidé en le nommant `useEffect`, surtout que tu vas voir : c'est un couteau suisse ce machin 😅
Pour faire court : `useEffect` permet de gérer le cycle de vie d'un composant fonctionnel _(comme `componentDidMount`, `componentDidUpdate` et `componentWillUnmount` pour les composants de classe)_.
Oui. Il fait tout. Tout seul.
Tu comprends pourquoi je dis "couteau suisse" ? 😏
Alors sur le papier c'est top, mais maintenant je te laisse t'amuser à comprendre comment ça fonctionne 😇
<tabs.reactUseEffectSyntaxes />
Pas cool, hein ? 😂
Et bien dans ces exemples, on a trois manières d'écrire un useEffect :
1. Le hook est exécuté une seule fois, après le premier rendu du composant
2. Le hook est exécuté à chaque mise à jour du composant
3. Le hook est exécuté à chaque mise à jour du composant, mais seulement si la propriété `uneProp` de `props` a changé
<Callout type="note" title="`useEffect` et les mises à jour du composant">
Alors quand je dis "le hook est exécuté à chaque mise à jour du composant", il faut également prendre en compte qu'il est également exécuté après le premier rendu du composant.
</Callout>
Mais alors, comment on fait pour gérer ces étapes avec des composants fonctionnels ?
Si tu n'as pas vu la différence entre les trois écritures, tu remarqueras que c'est le deuxième argument de useEffect qui fait la différence.
Le premier argument lui, est une fonction synchrone _(pas le droit de la rendre asynchrone !)_. On mettra dedans tout ce qu'on veut exécuter lors de l'appel du hook.
Le deuxième argument est un tableau de dépendances.
Selon ce tableau, le hook sera exécuté à des moments différents du cycle de vie du composant.
### ⚙️ ComponentDidMount
<tabs.reactUseEffectMount />
Le tableau de dépendances est vide, on sous-entend que le hook ne dépend d'aucune variable et sera exécuté une seule fois.
On peut donc dire que c'est l'équivalent de `componentDidMount` pour les composants de classe.
### 🔧 ComponentDidUpdate
<tabs.reactUseEffectUpdate />
Ici, le tableau de dépendances est absent _(et tout va bien, il est optionnel !)_.
Le hook sera exécuté à chaque mise à jour du composant, ainsi que lors du premier rendu.
<tabs.reactUseEffectUpdateDependency />
Dans ce cas, le tableau de dépendances contient la propriété `uneProp` de `props`.
Le hook sera exécuté à chaque mise à jour du composant _(ainsi qu'au montage)_, mais seulement si la propriété `uneProp` a changé.
### 🗑️ ComponentWillUnmount
Et là, tu te dis : "Mais comment je fais pour gérer le démontage du composant ?".
Hehehe, c'est là que ça devient intéressant 😏
<tabs.reactUseEffectUnmount />
Tu as vu ce petit `return` ? Et bien, c'est notre équivalent de `componentWillUnmount` pour les composants de classe !
Dans cette fonction de retour, on mettra tout ce qu'on veut exécuter avant la suppression du composant.
### 📦 Les dépendances
Tu as pu voir que le deuxième argument de `useEffect` est un tableau de dépendances.
Ce tableau est très important, car il permet de gérer les mises à jour du composant.
L'idée ici, c'est **d'optimiser** le rendu du composant en évitant de déclencher des mises à jour inutiles.
Pour éviter que React se dise "Tiens, il y a eu un changement, je vais re-rendre le composant", on va lui dire "Non, non, il n'y a pas eu de changement sur la prop que je t'ai fourni, tu peux rester tranquille".
### 🤓 Exemple concret
Allez, mettons un peu ce qu'on voit de voir en pratique !
Voici un exemple de code qui utilise `useEffect` pour gérer le cycle de vie d'un composant :
<tabs.reactUseEffectExample />
### 🔢 On revient sur le cycle de vie !
Et... stoooop !
On revient sur le cycle de vie d'un composant maintenant qu'on a vu `useEffect` en action !
Je vais te donner un exemple de code supplémentaire et tu vas devoir deviner l'ordre d'apparition des messages dans la console.
<tabs.reactUseEffectChallenge />
Voici les possibilités :
<Callout type="question" title="Quel est l'ordre d'apparition des messages dans la console ?">
- **A** - 4, 2, 1, 3
- **B** - 2, 4, 1, 3
- **C** - 1, 2, 3, 4
- **D** - La réponse D
</Callout>
## 🧩 Les autres hooks
On a vu les deux hooks les plus utilisés, mais il en existe d'autres qui peuvent être très utiles dans certaines situations.
Par exemple, on a :
- `useContext` : pour accéder à un contexte
- `useReducer` : pour gérer un state complexe
- `useCallback` : pour éviter les re-rendus inutiles
- `useMemo` : pour éviter les calculs inutiles
- `useRef` : pour accéder à un élément du DOM
Ne t'inquiète pas, on va les voir plus tard car le hook `useEffect` est déjà bien assez complexe à comprendre pour le moment 😅
## Conclusion
Et voilà, tu as maintenant toutes les clés en main pour gérer le cycle de vie de tes composants fonctionnels avec les hooks de React !
Vraiment, même si les autres hooks restent importants _(voire obligatoires dans certains contextes)_, tu as déjà de quoi faire de bons composants avec seulement ces deux là 😁

View File

@ -1,244 +0,0 @@
import { Snippet } from "@/components/Snippet";
const reactClassComponentSnippets = [
{
name: "JSX",
codeLanguage: "jsx",
withLineNumbers: true,
code: `import React from "react";
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
componentDidMount() {
console.log("Component mounted");
}
componentDidUpdate() {
console.log("Component updated");
}
componentWillUnmount() {
console.log("Component unmounted");
}
render() {
return <div>{this.state.count}</div>;
}
}`,
},
{
name: "TSX",
codeLanguage: "tsx",
withLineNumbers: true,
code: `import React from 'react';
type MyComponentState = {
count: number;
};
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state: MyComponentState = { count: 0 };
}
componentDidMount() {
console.log('Component mounted');
}
componentDidUpdate() {
console.log('Component updated');
}
componentWillUnmount() {
console.log('Component unmounted');
}
render() {
return <div>{this.state.count}</div>;
}
}`,
},
];
const reactUseEffectSyntaxesSnippets = [
{
name: "Syntaxe 1",
codeLanguage: "jsx",
code: `useEffect(() => {
// Code à exécuter
}, []);`,
},
{
name: "Syntaxe 2",
codeLanguage: "jsx",
code: `useEffect(() => {
// Code à exécuter
}, [props.uneProp]);`,
},
{
name: "Syntaxe 3",
codeLanguage: "jsx",
code: `useEffect(() => {
// Code à exécuter
});`,
},
];
const reactUseEffectMountSnippets = [
{
name: "Exemple de `useEffect` pour le montage",
codeLanguage: "jsx",
code: `useEffect(() => {
// Code à exécuter lors du montage
// (équivalent à componentDidMount)
}, []);`,
},
];
const reactUseEffectUpdateSnippets = [
{
name: "Exemple de `useEffect` pour la mise à jour",
codeLanguage: "jsx",
code: `useEffect(() => {
// Code à exécuter lors du montage et de la mise à jour
// (équivalent à componentDidMount et componentDidUpdate)
});`,
},
];
const reactUseEffectUpdateDependencySnippets = [
{
name: "Exemple de `useEffect` pour la mise à jour",
codeLanguage: "jsx",
code: `useEffect(() => {
// Code à exécuter lors du montage et de la mise à jour
// (équivalent à componentDidMount et componentDidUpdate)
}, [props.uneProp]);`,
},
];
const reactUseEffectUnmountSnippets = [
{
name: "Exemple de `useEffect` pour le démontage",
codeLanguage: "jsx",
code: `useEffect(() => {
// Code à exécuter lors du montage
return () => {
// Code à exécuter lors du démontage
// (équivalent à componentWillUnmount)
};
}, []);`,
},
];
const reactUseEffectExampleSnippets = [
{
name: "JSX",
codeLanguage: "jsx",
withLineNumbers: true,
code: `import React from "react";
export const Counter = () => {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
console.log("Component mounted");
return () => {
console.log("Component unmounted");
};
}, []);
React.useEffect(() => {
console.log("Component updated");
});
const increment = () => setCount(count + 1);
return <button onClick={increment}>{count}</button>;
};`,
},
{
name: "TSX",
codeLanguage: "tsx",
withLineNumbers: true,
code: `import React from "react";
export const Counter = () => {
const [count, setCount] = React.useState<number>(0);
React.useEffect(() => {
console.log("Component mounted");
return () => {
console.log("Component unmounted");
};
}, []);
React.useEffect(() => {
console.log("Component updated");
});
const increment = () => setCount(count + 1);
return <button onClick={increment}>{count}</button>;
};`,
},
];
const reactUseEffectChallengeSnippets = [
{
name: "Testons le cycle de vie d'un composant",
codeLanguage: "jsx",
withLineNumbers: true,
code: `import React from "react";
export const MyComponent = () => {
React.useEffect(() => {
console.log("1");
});
console.log("2");
React.useEffect(() => {
console.log("3");
}, []);
const logInRender = () => {
console.log("4");
return null;
};
return <div>{logInRender()}</div>;
};`,
},
];
export default {
reactClassComponent: () => <Snippet snippets={reactClassComponentSnippets} />,
reactUseEffectSyntaxes: () => (
<Snippet snippets={reactUseEffectSyntaxesSnippets} />
),
reactUseEffectMount: () => <Snippet snippets={reactUseEffectMountSnippets} />,
reactUseEffectUpdate: () => (
<Snippet snippets={reactUseEffectUpdateSnippets} />
),
reactUseEffectUpdateDependency: () => (
<Snippet snippets={reactUseEffectUpdateDependencySnippets} />
),
reactUseEffectUnmount: () => (
<Snippet snippets={reactUseEffectUnmountSnippets} />
),
reactUseEffectExample: () => (
<Snippet snippets={reactUseEffectExampleSnippets} />
),
reactUseEffectChallenge: () => (
<Snippet snippets={reactUseEffectChallengeSnippets} />
),
};

View File

@ -1,113 +0,0 @@
---
title: Le state et le cycle de vie d'un composant React
description: Voyons ensemble comment gérer le state et le cycle de vie d'un composant React !
tags: [Frontend, React, JavaScript, TypeScript, Bibliothèque, Interface utilisateur (UI)]
---
import Callout from "@/components/Callout";
import tabs from "./tabs";
Dans le précédent article, nous avons vu comment créer notre premier composant React avec notamment le concept de **props**.
Voyons maintenant comment gérer le **state** et le **cycle de vie** d'un composant React !
Commençons tranquillement avec le **cycle de vie** d'un composant, puisqu'il est indispensable pour comprendre le **state**.
## 🔄 Cycle de vie
Le **cycle de vie** d'un composant React est une série d'étapes que traverse un composant, de sa création _(montage)_ à sa destruction _(démontage)_.
Voici les trois différentes étapes du cycle de vie d'un composant React :
- **Montage** _(Mounting)_ : le composant est créé et inséré dans le DOM
- **Mise à jour** _(Updating)_ : le composant est mis à jour suite à un changement de props ou de state
- **Démontage** _(Unmounting)_ : le composant est retiré du DOM
On verra un peu plus en détail ces étapes dans l'article suivant qui traitera un certain hook de React : `useEffect`.
<Callout type="question" title="Hook, comme le capitaine ? 🦜🏴‍☠️">
Haha, non !
Un hook en React, est une fonction qui permet d'exploiter les fonctionnalités de React dans un composant fonctionnel _(fonction)_.
Bon... c'est un peu du charabia, mais on verra ça plus en détail dans le prochain article car il y a beaucoup à dire sur les hooks !
</Callout>
Mais pour le moment, restons en à une vue d'ensemble du cycle de vie !
## 🧠 State
Le state est un objet qui contient les données internes d'un composant.
Il est propre à chaque composant et peut être modifié par ce dernier (à ne pas confondre avec les props qui elles sont immuables).
Mais alors, pourquoi utiliser un state alors qu'on pourrait tout simplement déclarer une variable dans notre composant ?
Prenons cet exemple :
<tabs.reactLocalVariable />
[Voir l'exemple sur PlayCode](https://playcode.io/1940876)
On serait tentés de croire que ce code fonctionne. Après tout, en vanilla JS _(JavaScript pur)_, on pourrait tout à fait faire ça !
Et maintenant tu t'en doutes _(sinon pourquoi j'en parlerai ?)_, ce code ne fonctionne pas.
Pourtant, si on regarde la console du navigateur on voit bien que la variable `count` est bien incrémentée !
La raison est très simple : React ne sait pas que la variable `count` a été modifiée.
Pour être plus précis, React ne sait pas qu'il doit mettre à jour l'interface utilisateur _(UI)_ suite à la modification de count.
C'est là qu'intervient le **state** !
Le **state** est **réactif** et permet à React de savoir quand il doit mettre à jour l'interface utilisateur _(UI)_.
## 📝 Déclaration du state
Pour déclarer un **state**, on utilise le **hook** `useState` de React.
<tabs.reactStateDeclaration />
Et là tu vas peut-être te demander une chose...
<Callout type="question" title="Ouh là... Pourquoi on a deux assignements ?">
Bien vu ! Effectivement on va avoir deux assignements pour déclarer un state :
- `count` : la valeur du state
- `setCount` : la fonction qui permet de modifier la valeur du state
Si tu as déjà fait de la POO, le principe de **getter** et **setter** te sera familier puisque c'est un peu le même principe !
Le hook `useState` prend en paramètre la **valeur initiale du state** _(ici 0)_ et retourne un tableau avec la valeur du state et la fonction pour le modifier.
</Callout>
## 🔄 Utilisation du state
Maintenant que notre state est déclaré, on peut l'utiliser dans notre composant.
<tabs.reactStateUsage />
[Voir l'exemple sur PlayCode](https://playcode.io/1940705)
Et voilà ! Pas besoin de plus pour gérer un state en React 😉
Mais qu'est-ce qu'il se passe sous le capot ?
C'est un peu plus complexe que ça, mais pour faire simple :
### ⚙️ Montage du composant (Mounting)
On vient prévenir React que notre composant va avoir un **state** et on lui donne une valeur initiale _(ici 0)_.
### 🔧 Mise à jour du composant (Updating)
Ce state, à chaque modification, va déclencher un nouveau rendu du composant.
### 🗑️ Démontage du composant (Unmounting)
Et enfin, quand le composant est retiré du DOM, le state est détruit avec lui.
Ce fonctionnement est identique pour les props donnés à un composant d'ailleurs !
React est vraiment bien fait pour ça 😊
## Conclusion
Plutôt simple, non ?
Alors maintenant que tu sais comment gérer le **state** et le **cycle de vie** d'un composant React, tu es prêt à te pencher sur la prochaine étape _(et pas des moindres)_ : les **hooks** !
Mais pour l'heure, je te laisse jouer avec les **states** et les **props** pour bien comprendre comment tout ça fonctionne.

View File

@ -1,55 +0,0 @@
import { Snippet } from "@/components/Snippet";
const reactLocalVariableSnippets = [
{
name: "Variable locale au composant",
codeLanguage: "jsx",
withLineNumbers: true,
code: `import React from "react";
export function Counter() {
let count = 0;
function increment() {
count += 1;
console.log("Increment", count);
}
return <button onClick={increment}>{count}</button>;
}`,
},
];
const reactStateDeclarationSnippets = [
{
name: "Déclaration d'état avec le hook `useState`",
codeLanguage: "jsx",
code: "const [count, setCount] = React.useState(0);",
},
];
const reactStateUsageSnippets = [
{
name: "Utilisation de l'état avec le hook `useState`",
codeLanguage: "jsx",
code: `import React from "react";
export function Counter() {
const [count, setCount] = React.useState(0);
function increment() {
setCount(count + 1);
}
return <button onClick={increment}>{count}</button>;
}`,
},
];
export default {
reactLocalVariable: () => <Snippet snippets={reactLocalVariableSnippets} />,
reactStateDeclaration: () => (
<Snippet snippets={reactStateDeclarationSnippets} />
),
reactStateUsage: () => <Snippet snippets={reactStateUsageSnippets} />,
};

View File

@ -1,168 +0,0 @@
---
title: Le hook useContext de React
description: Découvrez comment utiliser le hook useContext de React pour gérer les contextes dans vos applications.
tags: [Frontend, React, JavaScript, TypeScript, Bibliothèque, Interface utilisateur (UI)]
---
import Callout from "@/components/Callout";
import tabs from "./tabs";
Les contextes sont un moyen de diffuser des données au travers des composants, sans avoir à les passer explicitement à chaque composant.
Pour faire simple, imaginons une arborescence de plusieurs composants imbriqués les uns dans les autres :
<tabs.reactNestedProps />
Fastidieux, n'est-ce pas ? On transmet à chaque fois les mêmes données, et ce, à chaque niveau de l'arborescence.
C'est là que les contextes entrent en jeu !
On va pouvoir alors déclarer notre contexte _(qui contiendra les données à diffuser)_ et le fournir à un niveau supérieur de l'arborescence.
## Déclaration d'un contexte
Avant de penser à notre contexte, on va réfléchir à ce que l'on veut diffuser et les valeurs par défaut.
Si on reprend notre exemple avec le thème clair et sombre, on sait que l'on va vouloir diffuser la valeur du thème et une fonction pour le changer.
On va donc préparer le terrain en créant un fichier `ThemeContext.jsx` _(ou `ThemeContext.tsx` si tu utilises TypeScript)_ :
<tabs.reactCreateContext />
## Fournir un contexte
Maintenant on peut le dire : notre contexte est prêt à être utilisé !
Il ne reste plus qu'à le fournir à notre arborescence de composants en lui créant un `Provider`.
<Callout type="question" title="Un provider ?">
Un `Provider` est un composant qui va permettre de **diffuser** les données du contexte à ses enfants.
Il est important de noter que le `Provider` doit **englober** les composants qui vont utiliser le contexte.
</Callout>
Un contexte React est un objet qui contient deux propriétés : `Provider` et `Consumer`.
Le `Provider` est un composant qui va permettre de diffuser les données du contexte à ses enfants.
Le `Consumer` est un composant qui va permettre de récupérer les données du contexte.
<tabs.reactContextProvider />
Mais on peut aller encore plus loin, en créant un Provider dédié à notre contexte !
Cela permettra de simplifier l'arborescence de composants et de rendre le code plus lisible :
<tabs.reactContextProviderWithValues />
Et pour terminer, on va maintenant pouvoir directement imbriquer notre `ThemeProvider` dans notre `App` :
<tabs.reactContextProviderInApp />
## Utilisation d'un contexte
C'est bien beau de créer un contexte, mais comment l'utiliser ?
Tu te souviens peut-être du `Consumer` que l'on a évoqué plus tôt, non ?
Et bien, il est temps de le mettre en pratique ! 😁
Pour commencer, nous allons avoir besoin du hook `useContext` de React.
Ce hook va nous permettre de récupérer les données du contexte, et ce, directement dans nos composants.
<tabs.reactUseContext />
Pas mal, non ? 😉
Fini l'arborescence de composants à rallonge, on peut maintenant récupérer les données du contexte directement dans nos composants !
## Les défauts des contextes
Seulement... Un grand pouvoir implique de grandes responsabilités. 🕷️
Bien que les contextes soient très pratiques, il faut prendre en compte quelques points :
- On ne peut pas utiliser les contextes pour tout et n'importe quoi. Ils sont plutôt adaptés pour diffuser des données qui sont utilisées par plusieurs composants.
- Les contextes peuvent rendre le code plus difficile à comprendre.
- L'utilisation de nombreux contextes va faire apparaître ce qu'on appelle le **context hell**.
### Le context hell
Dans cet article, nous avons vu comment créer un contexte et l'utiliser.
Et par chance, nous n'avons pas encore rencontré le **context hell**.
Mais maintenant, que se passe-t-il si on a besoin de plusieurs contextes _(plusieurs dizaines par exemple !)_ dans notre application ?
On va se retrouver avec une arborescence de composants qui va devenir de plus en plus difficile à comprendre et à maintenir.
Et c'est ça, le **context hell**.
<tabs.reactContextHell />
Maintenant, demande à un développeur d'inverser le provider `UserProvider` avec le provider `NoteProvider`.
C'est jouable sans difficulté, mais si tu entends des cris de désespoir, c'est normal. 😅
Pour éviter de tomber dans le **context hell**, il est important de bien réfléchir à l'utilisation des contextes dans notre application avec ces quelques questions :
- Est-ce que l'utilisation d'un contexte est vraiment nécessaire pour ce cas d'usage ?
- Est-ce que le contexte est utilisé par plusieurs composants ?
- Est-ce que le contexte est utilisé par des composants éloignés dans l'arborescence ?
Mais alors, si tu as besoin d'autant de contextes dans ton application, comment faire ?
Et bien, il existe des solutions pour éviter le **context hell** :
- Utiliser des bibliothèques tierces comme Redux _(solution lourde, mais très puissante)_
- Créer un nouveau composant qui va regrouper tous les contextes _(solution plus légère, mais plus difficile à maintenir)_
N'étant pas un grand fan de Redux, je vais plutôt te présenter la deuxième solution.
Mais si tu veux en savoir plus sur Redux, n'hésite pas à consulter la documentation officielle !
### Résoudre le context hell avec un composant dédié
Parlons de ce fameux composant qui va regrouper tous les contextes !
On ne parle pas ici d'un simple composant Providers qui va imbriquer tous les Provider de nos contextes, mais d'une solution plus élégante.
Après tout, nous sommes des feignants développeurs, non ? 😏
Réfléchissons à ce que l'on veut faire :
- On veut pouvoir regrouper tous les contextes dans un seul composant.
- On veut pouvoir ajouter ou supprimer des contextes facilement.
- On veut pouvoir facilement les ordonner entre eux.
- On veut éviter le **context hell**.
Et si on créait un composant Providers qui va nous permettre de faire tout ça ?
<tabs.reactContextProvidersComponent />
Ici on ne va pas remettre une cascade de Provider comme on a pu le voir plus tôt.
On va chercher à créer une fonction qui va nous permettre de les imbriquer les uns dans les autres.
<tabs.reactNestFunction />
<Callout type="note" title="React.cloneElement">
`React.cloneElement` est une fonction qui va permettre de cloner un élément React en lui passant de nouvelles propriétés.
Cela va nous permettre de créer une nouvelle arborescence de composants sans modifier l'arborescence actuelle.
Le premier argument est l'élément à cloner _(le composant)_, et le deuxième argument est un objet contenant les nouvelles propriétés.
Le troisième argument est le contenu de l'élément cloné _(les enfants)_.
</Callout>
Et maintenant, on va pouvoir utiliser notre fonction `nest` pour imbriquer nos Provider en utilisant la méthode `reduceRight` :
<tabs.reactNestFunctionWithReduceRight />
<Callout type="note" title="reduceRight">
`reduceRight` est une méthode qui va permettre de réduire un tableau _(ou un objet)_ en appliquant une fonction de rappel de droite à gauche.
Cela va nous permettre de réduire un tableau de `Provider` en les imbriquant les uns dans les autres sans se soucier de l'ordre _(qui est défini par le tableau)_.
Dans l'idée, on commence par le **dernier** élément du tableau, et on l'imbrique avec l'élément **précédent** du tableau et ainsi de suite jusqu'au **premier** élément du tableau.
Chaque itération va créer un nouvel élément imbriqué dans le précédent, en appelant la fonction `nest` qui est passée en argument.
</Callout>
Et voilà ! Il ne nous reste plus qu'à utiliser notre composant `Providers` pour regrouper tous nos `Provider` :
<tabs.reactCleanerProviders />
Évidemment le fichier contiendra toujours beaucoup de lignes, mais au moins, on a évité le **context hell** !
Il sera nettement plus facile de modifier l'ordre des Provider ou d'en ajouter de nouveaux.
## Conclusion
Ça casse un peu la tête, mais les contextes sont un outil très puissant pour diffuser des données dans nos applications React.
C'est aussi une excellente solution pour éviter d'utiliser des bibliothèques tierces comme Redux _(qui est très bien, mais qui peut être un peu lourd pour des petites applications)_.
On prendra d'ailleurs le temps de parler de Redux et de Zustand dans un prochain article 😉
Et si tu as besoin de plusieurs contextes dans ton application, n'oublie pas de réfléchir à l'utilisation de notre composant Providers pour éviter le **context hell**.

View File

@ -1,397 +0,0 @@
import { Snippet } from "@/components/Snippet";
import { name } from "eslint-plugin-prettier/recommended";
const reactNestedPropsSnippets = [
{
name: "JSX",
codeLanguage: "jsx",
withLineNumbers: true,
code: `import { useState } from "react";
const App = () => {
const [theme, setTheme] = useState("light");
return <A theme={theme} setTheme={theme} />;
};
const A = ({ theme, setTheme }) => {
return <B theme={theme} setTheme={setTheme} />;
};
const B = ({ theme, setTheme }) => {
return <C theme={theme} setTheme={setTheme} />;
};`,
},
{
name: "TSX",
codeLanguage: "tsx",
withLineNumbers: true,
code: `import type { Dispatch, SetStateAction } from "react";
import { useState } from "react";
type Theme = "light" | "dark";
const App = () => {
const [theme, setTheme] = useState<Theme>("light");
return <A theme={theme} setTheme={theme} />;
};
const A = ({ theme, setTheme }: { theme: Theme; setTheme: Dispatch<SetStateAction<Theme>> }) => {
return <B theme={theme} setTheme={setTheme} />;
};
const B = ({ theme, setTheme }: { theme: Theme; setTheme: Dispatch<SetStateAction<Theme>> }) => {
return <C theme={theme} setTheme={setTheme} />;
};`,
},
];
const reactCreateContextSnippets = [
{
name: "JSX",
codeLanguage: "jsx",
withLineNumbers: true,
code: `import { createContext } from "react";
// On crée notre contexte, avec une valeur par défaut : un thème clair
const ThemeContext = createContext({
theme: "light",
setTheme: () => {},
});`,
},
{
name: "TSX",
codeLanguage: "tsx",
withLineNumbers: true,
code: `import type { Dispatch, SetStateAction } from "react";
import { createContext } from "react";
// On crée un type pour les valeurs de thème
export type Theme = "light" | "dark";
// On crée un type pour notre contexte
type ThemeContextType = {
theme: Theme;
setTheme: Dispatch<SetStateAction<Theme>>;
};
// On crée notre contexte, avec une valeur par défaut : un thème clair
const ThemeContext = createContext<ThemeContextType>({
theme: "light",
setTheme: () => {},
});`,
},
];
const reactContextProviderSnippets = [
{
name: "JSX",
codeLanguage: "jsx",
withLineNumbers: true,
code: `import { useState } from "react";
const App = () => {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<A />
</ThemeContext.Provider>
);
};`,
},
{
name: "TSX",
codeLanguage: "tsx",
withLineNumbers: true,
code: `import type { Theme } from "./ThemeContext";
import { useState } from "react";
const App = () => {
const [theme, setTheme] = useState<Theme>("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<A />
</ThemeContext.Provider>
);
};`,
},
];
const reactContextProviderWithValuesSnippets = [
{
name: "JSX",
codeLanguage: "jsx",
withLineNumbers: true,
code: `import { createContext, useState } from "react";
const ThemeContext = createContext({
theme: "light",
setTheme: () => {},
});
const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState("light");
return <ThemeContext.Provider value={{ theme, setTheme }}>{children}</ThemeContext.Provider>;
};
export { ThemeContext, ThemeProvider };`,
},
{
name: "TSX",
codeLanguage: "tsx",
withLineNumbers: true,
code: `import type { ReactNode } from "react";
import { createContext, useState } from "react";
export type Theme = "light" | "dark";
type ThemeContextType = {
theme: Theme;
setTheme: Dispatch<SetStateAction<Theme>>;
};
const ThemeContext = createContext<ThemeContextType>({
theme: "light",
setTheme: () => {},
});
type ThemeProviderProps = {
children: ReactNode;
};
const ThemeProvider = ({ children }: ThemeProviderProps) => {
const [theme, setTheme] = useState<Theme>("light");
return <ThemeContext.Provider value={{ theme, setTheme }}>{children}</ThemeContext.Provider>;
};
export { ThemeContext, ThemeProvider };`,
},
];
const reactContextProviderInAppSnippets = [
{
name: "App.jsx",
codeLanguage: "jsx",
withLineNumbers: true,
code: `import { ThemeProvider } from "./ThemeContext";
import A from "./A";
const App = () => {
return (
<ThemeProvider>
<A />
</ThemeProvider>
);
};`,
},
];
const reactUseContextSnippets = [
{
name: "Utilisation du hook `useContext`",
codeLanguage: "jsx",
withLineNumbers: true,
code: `import { ThemeContext } from "./ThemeContext";
import { useContext } from "react";
const C = () => {
const { theme, setTheme } = useContext(ThemeContext);
return <>{/** JSX */}</>;
};`,
},
];
const reactContextHellSnippets = [
{
name: "Exemple de contexte imbriqué",
codeLanguage: "jsx",
withLineNumbers: true,
code: `root.render(
<StrictMode>
<UserProvider>
<ThemeProvider>
<LanguageProvider>
<PostProvider>
<SettingsProvider>
<SocketProvider>
<FriendProvider>
<NotificationProvider>
<ChatProvider>
<MusicProvider>
<VideoProvider>
<GameProvider>
<WeatherProvider>
<NewsProvider>
<CalendarProvider>
<TaskProvider>
<NoteProvider>
<App />
</NoteProvider>
</TaskProvider>
</CalendarProvider>
</NewsProvider>
</WeatherProvider>
</GameProvider>
</VideoProvider>
</MusicProvider>
</ChatProvider>
</NotificationProvider>
</FriendProvider>
</SocketProvider>
</SettingsProvider>
</PostProvider>
</LanguageProvider>
</ThemeProvider>
</UserProvider>
</StrictMode>,
);`,
},
];
const reactContextProvidersComponentSnippets = [
{
name: "JSX",
codeLanguage: "jsx",
withLineNumbers: true,
code: `const Providers = ({ providers, children }) => {
return (
<>
{/** Ouverture des providers */}
{children}
{/** Fermeture des providers */}
</>
);
};`,
},
{
name: "TSX",
codeLanguage: "tsx",
withLineNumbers: true,
code: `import type { ReactNode } from "react";
type ProvidersProps = {
providers: ReactNode[];
children: ReactNode;
};
const Providers = ({ providers, children }: ProvidersProps) => {
return (
<>
{/** Ouverture des providers */}
{children}
{/** Fermeture des providers */}
</>
);
};`,
},
];
const reactNestFunctionSnippets = [
{
name: "JSX",
codeLanguage: "jsx",
code: `const nest = (children, component) => {
return React.cloneElement(component, {}, children);
};`,
},
{
name: "TSX",
codeLanguage: "tsx",
code: `const nest = (children: ReactNode, component: ReactNode) => {
return React.cloneElement(component, {}, children);
};`,
},
];
const reactNestFunctionWithReduceRightSnippets = [
{
name: "JSX",
codeLanguage: "jsx",
code: `const nest = (children, component) => {
return React.cloneElement(component, {}, children);
};
const Providers = ({ providers, children }) => {
return providers.reduceRight(nest, children);
};`,
},
{
name: "TSX",
codeLanguage: "tsx",
code: `import type { ReactNode } from "react";
type ProvidersProps = {
providers: ReactNode[];
children: ReactNode;
};
const nest = (children: ReactNode, component: ReactNode) => {
return React.cloneElement(component, {}, children);
};
const Providers = ({ providers, children }: ProvidersProps) => {
return providers.reduceRight(nest, children);
};`,
},
];
const reactCleanerProvidersSnippets = [
{
name: "Import lisible et maintenable pour les providers",
codeLanguage: "jsx",
code: `root.render(
<StrictMode>
<Providers
providers={[
<UserProvider />,
<ThemeProvider />,
<LanguageProvider />,
<PostProvider />,
<SettingsProvider />,
<SocketProvider />,
<FriendProvider />,
// ...
]}
>
<App />
</Providers>
</StrictMode>,
);`,
},
];
export default {
reactNestedProps: () => <Snippet snippets={reactNestedPropsSnippets} />,
reactCreateContext: () => <Snippet snippets={reactCreateContextSnippets} />,
reactContextProvider: () => (
<Snippet snippets={reactContextProviderSnippets} />
),
reactContextProviderWithValues: () => (
<Snippet snippets={reactContextProviderWithValuesSnippets} />
),
reactContextProviderInApp: () => (
<Snippet snippets={reactContextProviderInAppSnippets} />
),
reactUseContext: () => <Snippet snippets={reactUseContextSnippets} />,
reactContextHell: () => <Snippet snippets={reactContextHellSnippets} />,
reactContextProvidersComponent: () => (
<Snippet snippets={reactContextProvidersComponentSnippets} />
),
reactNestFunction: () => <Snippet snippets={reactNestFunctionSnippets} />,
reactNestFunctionWithReduceRight: () => (
<Snippet snippets={reactNestFunctionWithReduceRightSnippets} />
),
reactCleanerProviders: () => (
<Snippet snippets={reactCleanerProvidersSnippets} />
),
};

View File

@ -1,308 +0,0 @@
---
title: L'utilisation des reducers avec React
description: Découvre les hooks de React, une fonctionnalité qui te permet de gérer le state et le cycle de vie de tes composants fonctionnels.
tags: [Frontend, React, JavaScript, TypeScript, Bibliothèque, Interface utilisateur (UI)]
---
Si tu as lu les précédentes pages concernant les hooks de React _(useState, useEffect et useContext)_, tu as déjà une bonne vision de la manière dont tu peux concevoir une application React.
Mais si je te dis que tu peux aller encore plus loin avec useReducer pour la gestion des états, est-ce que tu serais intéressé·e ? 🤔
{% callout type="question" title="Pourquoi ? useState ne suffit pas ?" %}
Le hook `useState` est génial et essentiel pour gérer l'état local d'un composant, mais il n'est pas adapté pour des états dits "complexes" ou pour des états qui dépendent les uns des autres.
{% /callout %}
## Qu'est-ce que le hook useReducer ?
Le hook `useReducer` est une alternative à `useState` qui est plus adaptée pour gérer des **états complexes** ou des **états qui dépendent les uns des autres**.
Il est basé sur le concept de **reducers** que l'on peut retrouver dans la bibliothèque Redux.
Un reducer est une fonction qui prend en paramètre un état et une action, et qui retourne un nouvel état. Il permet un découpage plus fin de la logique de gestion de l'état.
Mais avant de rentrer dans les détails, donnons des exemples de quand utiliser `useReducer` ou `useState` !
### Quand utiliser useState ?
Si tu dois stocker un état simple, comme un booléen, un nombre ou une chaîne de caractères, alors `useState` est parfait pour cela.
Ne te casse donc pas la tête à remplacer tous tes `useState` par des `useReducer` si tu n'en as pas besoin. 😅
### Quand utiliser useReducer ?
Dès que l'on a des états complexes ou des états qui dépendent les uns des autres, il est recommandé d'utiliser `useReducer`.
Par exemple, si tu as un formulaire avec plusieurs champs, et que tu veux gérer l'état de chaque champ de manière indépendante, alors `useReducer` est une bonne solution.
Ça te permettra d'éviter de créer des tonnes de `useState` et/ou handlers pour chaque champ du formulaire.
Mais `useReducer` n'est pas seulement utile pour les formulaires, il peut être utilisé dans de nombreux cas, comme la gestion d'un panier d'achat, la gestion d'une ressource, etc.
## À quoi ressemble un reducer ?
Comme expliqué plus tôt, un reducer est une fonction qui prend en paramètre un état et une action, et qui retourne un nouvel état.
Parlons dans un premier temps de la signature d'un reducer :
{% tabs defaultSelectedTab="jsx" %}
{% tab value="jsx" label="JSX" %}
{% snippet path="react/reducer/reducer-example.jsx" language="jsx" showLineNumbers=true /%}
{% /tab %}
{% tab value="tsx" label="TSX" %}
{% snippet path="react/reducer/reducer-example.tsx" language="tsx" showLineNumbers=true /%}
{% /tab %}
{% /tabs %}
Comme tu peux le voir, on récupère bien deux paramètres : `state` et `action`.
Le `state` est l'état actuel du composant, et l'`action` est un objet qui contient un type et d'autres propriétés.
En fonction du type de l'action, on retourne un nouvel état.
Par défaut _(c'est-à-dire si le type de l'action n'est pas reconnu)_, on retourne l'état actuel sans l'altérer.
Dans le reducer, il est strictement impossible d'altérer l'état actuel directement.
L'état est contraint au principe d'**immutabilité**.
On fera donc des `return` de l'état actuel avec les modifications nécessaires.
{% callout type="note" title="Pourquoi déverser le contenu de l'état actuel ?" %}
Si on ne déverse pas le contenu de l'état actuel, on perdrait les propriétés qui ne sont pas modifiées par l'action.
En déversant le contenu de l'état actuel, on s'assure de ne pas perdre ces propriétés.
Par exemple :
{% snippet path="react/reducer/reducer-why-spread-operator.jsx" language="jsx" showLineNumbers=true /%}
On perdrait ici la propriété `message` si on ne la déversait pas dans le nouvel état.
{% /callout %}
## Comment utiliser useReducer ?
Maintenant que tu as une idée de ce qu'est un reducer, voyons comment l'utiliser avec le hook `useReducer` au sein d'une application React ! 🚀
Naturellement, on va commencer par importer le hook `useReducer` :
```js
import { useReducer } from "react";
```
Ensuite, on va définir notre état initial :
{% tabs defaultSelectedTab="js" %}
{% tab value="js" label="JavaScript" %}
{% snippet path="react/reducer/reducer-initial-state.js" language="js" /%}
{% /tab %}
{% tab value="ts" label="TypeScript" %}
{% snippet path="react/reducer/reducer-initial-state.ts" language="ts" /%}
{% /tab %}
{% /tabs %}
On peut maintenant définir notre reducer :
{% tabs defaultSelectedTab="js" %}
{% tab value="js" label="JavaScript" %}
{% snippet path="react/reducer/reducer.js" language="js" showLineNumbers=true /%}
{% /tab %}
{% tab value="ts" label="TypeScript" %}
{% snippet path="react/reducer/reducer.ts" language="ts" showLineNumbers=true /%}
{% /tab %}
{% /tabs %}
{% callout type="question" title="C'est quoi `action.payload` ?" %}
La propriété `payload` de l'action est optionnelle. Il s'agit d'une convention pour passer des données à l'action.
Le `!` après `action.payload` signifie que l'on est sûr que `payload` est défini.
Cela permet d'éviter une erreur de type avec TypeScript.
Dans le cas du type `SET`, le payload sera défini obligatoirement avec un nombre qui sera la nouvelle valeur de la propriété `count` de l'état.
{% /callout %}
Enfin, on peut utiliser le hook useReducer dans notre composant :
{% tabs defaultSelectedTab="js" %}
{% tab value="js" label="JavaScript" %}
{% snippet path="react/reducer/reducer-hook.js" language="js" /%}
{% /tab %}
{% tab value="ts" label="TypeScript" %}
{% snippet path="react/reducer/reducer-hook.ts" language="ts" /%}
{% /tab %}
{% /tabs %}
`state` contient l'état actuel, et `dispatch` est une fonction qui permet d'envoyer une action au reducer.
Pour modifier l'état, on va donc appeler `dispatch` avec une action :
{% snippet path="react/reducer/reducer-dispatch-increment.js" language="js" /%}
Et voilà, tu sais maintenant comment utiliser `useReducer` dans une application React ! 🎉
## On nettoie tout ça !
Tout ce qui t'a été montré plus haut fonctionne, mais est-ce que pour autant ce code est qualitatif ? 🤔
**Non !**
Pourquoi ? Déjà, on a tout mis dans le même fichier, ce qui n'est pas très propre.
Mais surtout, notre code n'est pas à l'abri d'erreurs.
Que se passerait-il si on se trompait dans le type de l'action ?
Ou si on oubliait de passer un payload à l'action `SET` ?
C'est ce genre de comportements que l'on veut éviter pour nous assurer le bon fonctionnement de notre application.
Pour contrer ces problèmes, on va créer des actions et des types d'actions pour garantir la cohérence de notre code.
### Création des types d'actions
Nos types d'actions seront tous des chaînes de caractères. On va donc pouvoir les définir sous forme de constantes.
{% snippet path="react/reducer/reducer-actions-constants.js" language="js" /%}
{% callout type="note" title="Regrouper les exports" %}
Et là, tu te dis : "Pourquoi ne pas regrouper les exports dans un seul objet ?"
Bien vu ! Et pour TypeScript, on peut aller encore plus loin en créant un `enum` pour les types d'actions 😉
{% tabs defaultSelectedTab="js" %}
{% tab value="js" label="JavaScript" %}
{% snippet path="react/reducer/reducer-actions-enum.js" language="js" /%}
{% /tab %}
{% tab value="ts" label="TypeScript" %}
{% snippet path="react/reducer/reducer-actions-enum.ts" language="ts" /%}
{% /tab %}
{% /tabs %}
{% /callout %}
### Typage des actions
Si tu utilises JavaScript, je suis désolé de te dire que tu ne peux pas **fortement** typer les actions.
En revanche, si tu utilises TypeScript, tu peux définir les actions de la manière suivante :
{% snippet path="react/reducer/reducer-actions-union.ts" language="ts" /%}
Tu pourras alors utiliser `CounterAction` pour typer les actions de ton reducer :
{% snippet path="react/reducer/reducer-actions-union-use.ts" language="ts" /%}
### Action creators
Pour éviter de se tromper dans le type de l'action, on peut se créer des fonctions qui vont nous permettre de créer des actions.
{% tabs defaultSelectedTab="js" %}
{% tab value="js" label="JavaScript" %}
{% snippet path="react/reducer/reducer-action-creator.js" language="js" /%}
{% /tab %}
{% tab value="ts" label="TypeScript" %}
{% snippet path="react/reducer/reducer-action-creator.ts" language="ts" /%}
{% /tab %}
{% /tabs %}
Maintenant le dispatch de nos actions sera beaucoup plus simple et éviter davantage les erreurs lors du développement !
{% snippet path="react/reducer/reducer-dispatch-action-creator.js" language="js" /%}
## Les fichiers complets
On a vu beaucoup de chose et les fichiers sont un peu éparpillés.
Pour t'aider à mieux comprendre le fonctionnement du hook `useReducer` et comment l'implementer, voici les fichiers complets :
### Fichier counterReducer.js ou counterReducer.ts
{% tabs defaultSelectedTab="js" %}
{% tab value="js" label="JavaScript" %}
{% snippet path="react/reducer/file-counterReducer.js" language="js" showLineNumbers=true label="src/reducers/counterReducer.js" /%}
{% /tab %}
{% tab value="ts" label="TypeScript" %}
{% snippet path="react/reducer/file-counterReducer.ts" language="ts" showLineNumbers=true label="src/reducers/counterReducer.ts" /%}
{% /tab %}
{% /tabs %}
### Fichier Counter.jsx ou Counter.tsx
{% tabs defaultSelectedTab="jsx" %}
{% tab value="jsx" label="JSX" %}
{% snippet path="react/reducer/file-counter.jsx" language="jsx" showLineNumbers=true label="src/components/Counter.jsx" /%}
{% /tab %}
{% tab value="tsx" label="TSX" %}
{% snippet path="react/reducer/file-counter.tsx" language="tsx" showLineNumbers=true label="src/components/Counter.tsx" /%}
{% /tab %}
{% /tabs %}
## C'est l'heure des questions !
{% callout type="question" title="Quand utiliser `useReducer` ?" %}
- **A** - Pour des états simples
- **B** - Pour des états complexes ou des états qui dépendent les uns des autres
{% /callout %}
{% callout type="question" title="Quelle est la signature d'un reducer ?" %}
- **A** - `(state, action) => { /* ... */ }`
- **B** - `(action, state) => { /* ... */ }`
- **C** - `(state) => { /* ... */ }`
- **D** - `(action) => { /* ... */ }`
{% /callout %}
{% callout type="question" title="Pourquoi déverser le contenu de l'état actuel dans le nouvel état ?" %}
- **A** - Pour rendre le code plus lisible
- **B** - Pour ne pas perdre les propriétés qui ne sont pas modifiées par l'action
{% /callout %}
{% callout type="question" title="Pourquoi utiliser des constantes pour les types d'actions ?" %}
- **A** - Pour rendre le code plus lisible
- **B** - Pour alourdir inutillement le code
- **C** - Pour éviter de se tromper dans le type de l'action
{% /callout %}
## Conclusion
Alors, pas trop fatigué·e ? 😅
Au moins ça en valait la peine ! Tu te feras moins de nœuds au cerveau par la suite dans tes projets React !
Comme tu as pu le voir, `useReducer` est un outil puissant pour gérer des états complexes ou des états qui dépendent les uns des autres.
Même si ici notre exemple n'était qu'un simple compteur, tu peux appliquer tout ce que tu as pu voir dans des cas plus concrets.
Si tu veux aller encore plus loin, n'hésite pas à jeter un œil à la documentation officielle de React pour `useReducer`.
Tu peux également te renseigner sur Redux si tu veux aller encore plus loin dans la gestion de l'état de ton application, mais attention, c'est une autre paire de manches ! 😄