diff --git a/app-old/data/snippets/react/reducer/reducer-actions-union.ts b/app-old/data/snippets/react/reducer/reducer-actions-union.ts
index 5e3df71..35336df 100644
--- a/app-old/data/snippets/react/reducer/reducer-actions-union.ts
+++ b/app-old/data/snippets/react/reducer/reducer-actions-union.ts
@@ -1,4 +1,3 @@
-
export type CounterAction =
| { type: CounterActionTypes.INCREMENT }
| { type: CounterActionTypes.DECREMENT }
diff --git a/app/layouts/prism.css b/app/layouts/prism.css
index 2947fda..a3d5322 100644
--- a/app/layouts/prism.css
+++ b/app/layouts/prism.css
@@ -61,7 +61,8 @@
}
.token.property,
-.token.tag {
+.token.tag,
+.token.constant {
color: #E55649;
}
diff --git a/app/pages/docs/react/use-context/tabs.tsx b/app/pages/docs/react/use-context/tabs.tsx
index a8e091c..7ca0231 100644
--- a/app/pages/docs/react/use-context/tabs.tsx
+++ b/app/pages/docs/react/use-context/tabs.tsx
@@ -1,5 +1,4 @@
import { Snippet } from "@/components/Snippet";
-import { name } from "eslint-plugin-prettier/recommended";
const reactNestedPropsSnippets = [
{
diff --git a/app/pages/docs/react/use-reducer/page.md b/app/pages/docs/react/use-reducer/+Page.mdx
similarity index 52%
rename from app/pages/docs/react/use-reducer/page.md
rename to app/pages/docs/react/use-reducer/+Page.mdx
index d8440b7..0335855 100644
--- a/app/pages/docs/react/use-reducer/page.md
+++ b/app/pages/docs/react/use-reducer/+Page.mdx
@@ -4,15 +4,16 @@ description: Découvre les hooks de React, une fonctionnalité qui te permet de
tags: [Frontend, React, JavaScript, TypeScript, Bibliothèque, Interface utilisateur (UI)]
---
+import Callout from "@/components/Callout";
+import tabs from "./tabs";
+
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 %}
+
+ 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.
+
## Qu'est-ce que le hook useReducer ?
@@ -46,17 +47,7 @@ Comme expliqué plus tôt, un reducer est une fonction qui prend en paramètre u
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`.
@@ -70,19 +61,17 @@ 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.
-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.
-En déversant le contenu de l'état actuel, on s'assure de ne pas perdre ces propriétés.
+ Par exemple :
-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 %}
+ On perdrait ici la propriété `message` si on ne la déversait pas dans le nouvel état.
+
## Comment utiliser useReducer ?
@@ -90,68 +79,34 @@ Maintenant que tu as une idée de ce qu'est un reducer, voyons comment l'utilise
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 %}
+
+ La propriété `payload` de l'action est optionnelle. Il s'agit d'une convention pour passer des données à l'action.
-{% tab value="ts" label="TypeScript" %}
-{% snippet path="react/reducer/reducer.ts" language="ts" showLineNumbers=true /%}
-{% /tab %}
+ 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.
-{% /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 %}
+ 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.
+
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 ! 🎉
@@ -175,27 +130,15 @@ Pour contrer ces problèmes, on va créer des actions et des types d'actions pou
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 ?"
-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 😉
-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
@@ -203,31 +146,21 @@ Si tu utilises JavaScript, je suis désolé de te dire que tu ne peux pas **fort
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
@@ -236,64 +169,36 @@ Pour t'aider à mieux comprendre le fonctionnement du hook `useReducer` et comme
### 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
+
-- **A** - Pour des états simples
-- **B** - Pour des états complexes ou des états qui dépendent les uns des autres
+
+ - **A** - `(state, action) => { /* ... */ }`
+ - **B** - `(action, state) => { /* ... */ }`
+ - **C** - `(state) => { /* ... */ }`
+ - **D** - `(action) => { /* ... */ }`
+
-{% /callout %}
+
+ - **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 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 %}
+
+ - **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
+
## Conclusion
diff --git a/app/pages/docs/react/use-reducer/tabs.tsx b/app/pages/docs/react/use-reducer/tabs.tsx
new file mode 100644
index 0000000..28859a9
--- /dev/null
+++ b/app/pages/docs/react/use-reducer/tabs.tsx
@@ -0,0 +1,420 @@
+import { Snippet } from "@/components/Snippet";
+
+const reactReducerExampleSnippets = [
+ {
+ name: "JSX",
+ codeLanguage: "jsx",
+ withLineNumbers: true,
+ code: `const reducer = (state, action) => {
+ switch (action.type) {
+ case "TYPE_1":
+ return { ...state /* Nouvel état */ };
+ case "TYPE_2":
+ return { ...state /* Nouvel état */ };
+ default:
+ return state;
+ }
+};`,
+ },
+ {
+ name: "TSX",
+ codeLanguage: "tsx",
+ withLineNumbers: true,
+ code: `const reducer = (state: State, action: Action) => {
+ switch (action.type) {
+ case "TYPE_1":
+ return { ...state /* Nouvel état */ };
+ case "TYPE_2":
+ return { ...state /* Nouvel état */ };
+ default:
+ return state;
+ }
+};`,
+ },
+];
+
+const reactReducerWhySpreadOperatorSnippets = [
+ {
+ name: "Retour sans déversement de l'état",
+ codeLanguage: "jsx",
+ withLineNumbers: true,
+ code: `const initialState = { count: 0, message: "Hello" };
+
+const reducer = (state, action) => {
+ switch (action.type) {
+ case "INCREMENT":
+ return { count: state.count + 1 };
+ default:
+ return state;
+ }
+};`,
+ },
+];
+
+const reactUseReducerImportSnippets = [
+ {
+ name: "Importation de useReducer",
+ codeLanguage: "jsx",
+ code: `import { useReducer } from "react";`,
+ },
+];
+
+const reactReducerInitialStateSnippets = [
+ {
+ name: "JSX",
+ codeLanguage: "jsx",
+ code: "const initialState = { count: 0 };",
+ },
+ {
+ name: "TSX",
+ codeLanguage: "tsx",
+ code: `type State = {
+ count: number;
+};
+const initialState: State = { count: 0 };`,
+ },
+];
+
+const reactCounterReducerSnippets = [
+ {
+ name: "JSX",
+ codeLanguage: "jsx",
+ withLineNumbers: true,
+ code: `const reducer = (state, action) => {
+ switch (action.type) {
+ case "INCREMENT":
+ return { ...state, count: state.count + 1 };
+ case "DECREMENT":
+ return { ...state, count: state.count - 1 };
+ case "RESET":
+ return { ...state, count: 0 };
+ case "SET":
+ return { ...state, count: action.payload };
+ default:
+ return state;
+ }
+};`,
+ },
+ {
+ name: "TSX",
+ codeLanguage: "tsx",
+ withLineNumbers: true,
+ code: `type State = {
+ count: number;
+};
+
+type Action = {
+ type: "INCREMENT" | "DECREMENT" | "RESET" | "SET";
+ payload?: number;
+};
+
+const reducer = (state: State, action: Action) => {
+ switch (action.type) {
+ case "INCREMENT":
+ return { ...state, count: state.count + 1 };
+ case "DECREMENT":
+ return { ...state, count: state.count - 1 };
+ case "RESET":
+ return { ...state, count: 0 };
+ case "SET":
+ return { ...state, count: action.payload! };
+ default:
+ return state;
+ }
+};`,
+ },
+];
+
+const reactUseReducerUsageSnippets = [
+ {
+ name: "JSX",
+ codeLanguage: "jsx",
+ code: "const [state, dispatch] = useReducer(reducer, initialState);",
+ },
+ {
+ name: "TSX",
+ codeLanguage: "tsx",
+ code: "const [state, dispatch] = useReducer(reducer, initialState);",
+ },
+];
+
+const reactDispatchIncrementSnippets = [
+ {
+ name: "Exemple d'utilisation de dispatch",
+ codeLanguage: "jsx",
+ code: `dispatch({ type: "INCREMENT" });`,
+ },
+];
+
+const reactActionsConstantsSnippets = [
+ {
+ name: "Création et exportation de constantes d'actions",
+ codeLanguage: "jsx",
+ code: `export const INCREMENT = "INCREMENT";
+export const DECREMENT = "DECREMENT";
+export const RESET = "RESET";
+export const SET = "SET";`,
+ },
+];
+
+const reactActionsEnumSnippets = [
+ {
+ name: "JavaScript",
+ codeLanguage: "javascript",
+ code: `export const CounterActionTypes = {
+ INCREMENT: "INCREMENT",
+ DECREMENT: "DECREMENT",
+ RESET: "RESET",
+ SET: "SET",
+};`,
+ },
+ {
+ name: "TypeScript",
+ codeLanguage: "typescript",
+ code: `export const enum CounterActionTypes {
+ INCREMENT = "INCREMENT",
+ DECREMENT = "DECREMENT",
+ RESET = "RESET",
+ SET = "SET",
+};`,
+ },
+];
+
+const reactActionsUnionSnippets = [
+ {
+ name: "Union de types d'actions",
+ codeLanguage: "typescript",
+ code: `export type CounterAction =
+ | { type: CounterActionTypes.INCREMENT }
+ | { type: CounterActionTypes.DECREMENT }
+ | { type: CounterActionTypes.RESET }
+ | { type: CounterActionTypes.SET; payload: number };`,
+ },
+];
+
+const reactActionsUnionUsageSnippets = [
+ {
+ name: "Utilisation de l'union de types d'actions",
+ codeLanguage: "typescript",
+ code: `const reducer = (state: State, action: CounterAction) => {
+ switch (action.type) {
+ case CounterActionTypes.INCREMENT:
+ return { ...state, count: state.count + 1 };
+ case CounterActionTypes.DECREMENT:
+ return { ...state, count: state.count - 1 };
+ case CounterActionTypes.RESET:
+ return { ...state, count: 0 };
+ case CounterActionTypes.SET:
+ return { ...state, count: action.payload };
+ default:
+ return state;
+ }
+};`,
+ },
+];
+
+const reactActionCreatorSnippets = [
+ {
+ name: "JavaScript",
+ codeLanguage: "javascript",
+ code: `export const actions = {
+ increment: () => ({ type: CounterActionTypes.INCREMENT }),
+ decrement: () => ({ type: CounterActionTypes.DECREMENT }),
+ reset: () => ({ type: CounterActionTypes.RESET }),
+ set: (value) => ({ type: CounterActionTypes.SET, payload: value }),
+};`,
+ },
+ {
+ name: "TypeScript",
+ codeLanguage: "typescript",
+ code: `export const actions = {
+ increment: (): CounterAction => ({ type: CounterActionTypes.INCREMENT }),
+ decrement: (): CounterAction => ({ type: CounterActionTypes.DECREMENT }),
+ reset: (): CounterAction => ({ type: CounterActionTypes.RESET }),
+ set: (value: number): CounterAction => ({ type: CounterActionTypes.SET, payload: value }),
+};`,
+ },
+];
+
+const reactDispatchActionCreatorSnippets = [
+ {
+ name: "Utilisation de l'action créateur avec dispatch",
+ codeLanguage: "javascript",
+ code: `dispatch(actions.increment());
+dispatch(actions.set(10));`,
+ },
+];
+
+const reactFileCounterReducerSnippets = [
+ {
+ name: "app/reducers/counterReducer.js",
+ codeLanguage: "javascript",
+ code: `const CounterActionTypes = {
+ INCREMENT: "INCREMENT",
+ DECREMENT: "DECREMENT",
+ RESET: "RESET",
+ SET: "SET",
+};
+
+export const initialState = { count: 0 };
+
+export const reducer = (state, action) => {
+ switch (action.type) {
+ case CounterActionTypes.INCREMENT:
+ return { ...state, count: state.count + 1 };
+ case CounterActionTypes.DECREMENT:
+ return { ...state, count: state.count - 1 };
+ case CounterActionTypes.RESET:
+ return { ...state, count: 0 };
+ case CounterActionTypes.SET:
+ return { ...state, count: action.payload };
+ default:
+ return state;
+ }
+};
+
+export const actions = {
+ increment: () => ({ type: CounterActionTypes.INCREMENT }),
+ decrement: () => ({ type: CounterActionTypes.DECREMENT }),
+ reset: () => ({ type: CounterActionTypes.RESET }),
+ set: (value) => ({ type: CounterActionTypes.SET, payload: value }),
+};`,
+ },
+ {
+ name: "app/reducers/counterReducer.ts",
+ codeLanguage: "typescript",
+ code: `const enum CounterActionTypes {
+ INCREMENT = "INCREMENT",
+ DECREMENT = "DECREMENT",
+ RESET = "RESET",
+ SET = "SET",
+}
+
+type State = {
+ count: number;
+};
+
+type Action =
+ | { type: CounterActionTypes.INCREMENT }
+ | { type: CounterActionTypes.DECREMENT }
+ | { type: CounterActionTypes.RESET }
+ | { type: CounterActionTypes.SET; payload: number };
+
+export const initialState: State = { count: 0 };
+
+export const reducer = (state: State, action: Action) => {
+ switch (action.type) {
+ case CounterActionTypes.INCREMENT:
+ return { ...state, count: state.count + 1 };
+ case CounterActionTypes.DECREMENT:
+ return { ...state, count: state.count - 1 };
+ case CounterActionTypes.RESET:
+ return { ...state, count: 0 };
+ case CounterActionTypes.SET:
+ return { ...state, count: action.payload };
+ default:
+ return state;
+ }
+};
+
+export const actions = {
+ increment: (): Action => ({ type: CounterActionTypes.INCREMENT }),
+ decrement: (): Action => ({ type: CounterActionTypes.DECREMENT }),
+ reset: (): Action => ({ type: CounterActionTypes.RESET }),
+ set: (value: number): Action => ({ type: CounterActionTypes.SET, payload: value }),
+};`,
+ },
+];
+
+const reactFileCounterComponentSnippets = [
+ {
+ name: "app/components/Counter.jsx",
+ codeLanguage: "jsx",
+ code: `import { initialState, actions, reducer } from "../reducers/counterReducer";
+import { useReducer } from "react";
+
+const Counter = () => {
+ const [state, dispatch] = useReducer(reducer, initialState);
+
+ return (
+
+
Count: {state.count}
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Counter;`,
+ },
+ {
+ name: "app/components/Counter.tsx",
+ codeLanguage: "tsx",
+ code: `import { initialState, actions, reducer } from "../reducers/counterReducer";
+import { useReducer } from "react";
+
+const Counter = () => {
+ const [state, dispatch] = useReducer(reducer, initialState);
+
+ return (
+
+
Count: {state.count}
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Counter;`,
+ },
+];
+
+export default {
+ reactReducerExample: () => ,
+ reactReducerWhySpreadOperator: () => (
+
+ ),
+ reactUseReducerImport: () => (
+
+ ),
+ reactReducerInitialState: () => (
+
+ ),
+ reactCounterReducer: () => ,
+ reactUseReducerUsage: () => (
+
+ ),
+ reactDispatchIncrement: () => (
+
+ ),
+ reactActionsConstants: () => (
+
+ ),
+ reactActionsEnum: () => ,
+ reactActionsUnion: () => ,
+ reactActionsUnionUsage: () => (
+
+ ),
+ reactActionCreator: () => ,
+ reactDispatchActionCreator: () => (
+
+ ),
+ reactFileCounterReducer: () => (
+
+ ),
+ reactFileCounterComponent: () => (
+
+ ),
+};