feat: Add Counter component with reducers

This commit is contained in:
Gauthier Daniels 2025-04-13 17:12:10 +02:00
parent 82b898e9eb
commit 79195a1892
14 changed files with 183 additions and 220 deletions

View File

@ -47,6 +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 : Parlons dans un premier temps de la signature d'un reducer :
{% tabs defaultSelectedTab="jsx" %} {% tabs defaultSelectedTab="jsx" %}
{% tab value="jsx" label="JSX" %} {% tab value="jsx" label="JSX" %}
{% snippet path="react/reducer/reducer-example.jsx" language="jsx" showLineNumbers=true /%} {% snippet path="react/reducer/reducer-example.jsx" language="jsx" showLineNumbers=true /%}
{% /tab %} {% /tab %}
@ -54,6 +55,7 @@ Parlons dans un premier temps de la signature d'un reducer :
{% tab value="tsx" label="TSX" %} {% tab value="tsx" label="TSX" %}
{% snippet path="react/reducer/reducer-example.tsx" language="tsx" showLineNumbers=true /%} {% snippet path="react/reducer/reducer-example.tsx" language="tsx" showLineNumbers=true /%}
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
Comme tu peux le voir, on récupère bien deux paramètres : `state` et `action`. Comme tu peux le voir, on récupère bien deux paramètres : `state` et `action`.
@ -95,16 +97,13 @@ import { useReducer } from "react";
Ensuite, on va définir notre état initial : Ensuite, on va définir notre état initial :
{% tabs defaultSelectedTab="js" %} {% tabs defaultSelectedTab="js" %}
{% tab value="js" label="JavaScript" %} {% tab value="js" label="JavaScript" %}
{% snippet path="react/reducer/reducer-initial-state.js" language="js" /%} {% snippet path="react/reducer/reducer-initial-state.js" language="js" /%}
{% /tab %} {% /tab %}
{% tab value="ts" label="TypeScript" %} {% tab value="ts" label="TypeScript" %}
{% snippet path="react/reducer/reducer-initial-state.ts" language="ts" /%} {% snippet path="react/reducer/reducer-initial-state.ts" language="ts" /%}
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
@ -114,15 +113,11 @@ On peut maintenant définir notre reducer :
{% tabs defaultSelectedTab="js" %} {% tabs defaultSelectedTab="js" %}
{% tab value="js" label="JavaScript" %} {% tab value="js" label="JavaScript" %}
{% snippet path="react/reducer/reducer.js" language="js" showLineNumbers=true /%} {% snippet path="react/reducer/reducer.js" language="js" showLineNumbers=true /%}
{% /tab %} {% /tab %}
{% tab value="ts" label="TypeScript" %} {% tab value="ts" label="TypeScript" %}
{% snippet path="react/reducer/reducer.ts" language="ts" showLineNumbers=true /%} {% snippet path="react/reducer/reducer.ts" language="ts" showLineNumbers=true /%}
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
@ -143,15 +138,11 @@ Enfin, on peut utiliser le hook useReducer dans notre composant :
{% tabs defaultSelectedTab="js" %} {% tabs defaultSelectedTab="js" %}
{% tab value="js" label="JavaScript" %} {% tab value="js" label="JavaScript" %}
{% snippet path="react/reducer/reducer-hook.js" language="js" /%} {% snippet path="react/reducer/reducer-hook.js" language="js" /%}
{% /tab %} {% /tab %}
{% tab value="ts" label="TypeScript" %} {% tab value="ts" label="TypeScript" %}
{% snippet path="react/reducer/reducer-hook.ts" language="ts" /%} {% snippet path="react/reducer/reducer-hook.ts" language="ts" /%}
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
@ -160,9 +151,7 @@ Enfin, on peut utiliser le hook useReducer dans notre composant :
Pour modifier l'état, on va donc appeler `dispatch` avec une action : Pour modifier l'état, on va donc appeler `dispatch` avec une action :
```js {% snippet path="react/reducer/reducer-dispatch-increment.js" language="js" /%}
dispatch({ type: "INCREMENT" });
```
Et voilà, tu sais maintenant comment utiliser `useReducer` dans une application React ! 🎉 Et voilà, tu sais maintenant comment utiliser `useReducer` dans une application React ! 🎉
@ -186,12 +175,7 @@ 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. Nos types d'actions seront tous des chaînes de caractères. On va donc pouvoir les définir sous forme de constantes.
```js {% snippet path="react/reducer/reducer-actions-constants.js" language="js" /%}
export const INCREMENT = "INCREMENT";
export const DECREMENT = "DECREMENT";
export const RESET = "RESET";
export const SET = "SET";
```
{% callout type="note" title="Regrouper les exports" %} {% callout type="note" title="Regrouper les exports" %}
@ -202,29 +186,11 @@ Bien vu ! Et pour TypeScript, on peut aller encore plus loin en créant un `enum
{% tabs defaultSelectedTab="js" %} {% tabs defaultSelectedTab="js" %}
{% tab value="js" label="JavaScript" %} {% tab value="js" label="JavaScript" %}
{% snippet path="react/reducer/reducer-actions-enum.js" language="js" /%}
```js
export const CounterActionTypes = {
INCREMENT: "INCREMENT",
DECREMENT: "DECREMENT",
RESET: "RESET",
SET: "SET",
};
```
{% /tab %} {% /tab %}
{% tab value="ts" label="TypeScript" %} {% tab value="ts" label="TypeScript" %}
{% snippet path="react/reducer/reducer-actions-enum.ts" language="ts" /%}
```ts
export const enum CounterActionTypes {
INCREMENT = "INCREMENT",
DECREMENT = "DECREMENT",
RESET = "RESET",
SET = "SET",
}
```
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
@ -237,32 +203,11 @@ 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 : En revanche, si tu utilises TypeScript, tu peux définir les actions de la manière suivante :
```ts {% snippet path="react/reducer/reducer-actions-union.ts" language="ts" /%}
export type CounterAction =
| { type: CounterActionTypes.INCREMENT }
| { type: CounterActionTypes.DECREMENT }
| { type: CounterActionTypes.RESET }
| { type: CounterActionTypes.SET; payload: number };
```
Tu pourras alors utiliser `CounterAction` pour typer les actions de ton reducer : Tu pourras alors utiliser `CounterAction` pour typer les actions de ton reducer :
```ts {% snippet path="react/reducer/reducer-actions-union-use.ts" language="ts" /%}
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;
}
};
```
### Action creators ### Action creators
@ -271,195 +216,48 @@ Pour éviter de se tromper dans le type de l'action, on peut se créer des fonct
{% tabs defaultSelectedTab="js" %} {% tabs defaultSelectedTab="js" %}
{% tab value="js" label="JavaScript" %} {% tab value="js" label="JavaScript" %}
{% snippet path="react/reducer/reducer-action-creator.js" language="js" /%}
```js
export const actions = {
increment: () => ({ type: CounterActionTypes.INCREMENT }),
decrement: () => ({ type: CounterActionTypes.DECREMENT }),
reset: () => ({ type: CounterActionTypes.RESET }),
set: (value) => ({ type: CounterActionTypes.SET, payload: value }),
};
```
{% /tab %} {% /tab %}
{% tab value="ts" label="TypeScript" %} {% tab value="ts" label="TypeScript" %}
{% snippet path="react/reducer/reducer-action-creator.ts" language="ts" /%}
```ts
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 }),
};
```
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
Maintenant le dispatch de nos actions sera beaucoup plus simple et éviter davantage les erreurs lors du développement ! Maintenant le dispatch de nos actions sera beaucoup plus simple et éviter davantage les erreurs lors du développement !
```js {% snippet path="react/reducer/reducer-dispatch-action-creator.js" language="js" /%}
dispatch(actions.increment());
dispatch(actions.set(10));
```
## Les fichiers complets ## Les fichiers complets
On a vu beaucoup de chose et les fichiers sont un peu éparpillés. 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 : Pour t'aider à mieux comprendre le fonctionnement du hook `useReducer` et comment l'implementer, voici les fichiers complets :
### Fichier `counterReducer.js` ou `counterReducer.ts` ### Fichier counterReducer.js ou counterReducer.ts
{% tabs defaultSelectedTab="js" %} {% tabs defaultSelectedTab="js" %}
{% tab value="js" label="JavaScript" %} {% tab value="js" label="JavaScript" %}
{% snippet path="react/reducer/file-counterReducer.js" language="js" showLineNumbers=true label="src/reducers/counterReducer.js" /%}
```js
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 }),
};
```
{% /tab %} {% /tab %}
{% tab value="ts" label="TypeScript" %} {% tab value="ts" label="TypeScript" %}
{% snippet path="react/reducer/file-counterReducer.ts" language="ts" showLineNumbers=true label="src/reducers/counterReducer.ts" /%}
```ts
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 }),
};
```
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
### Fichier `Counter.jsx` ou `Counter.tsx` ### Fichier Counter.jsx ou Counter.tsx
{% tabs defaultSelectedTab="jsx" %} {% tabs defaultSelectedTab="jsx" %}
{% tab value="jsx" label="JSX" %} {% tab value="jsx" label="JSX" %}
{% snippet path="react/reducer/file-counter.jsx" language="jsx" showLineNumbers=true label="src/components/Counter.jsx" /%}
```jsx
import { initialState, actions, reducer } from "../reducers/counterReducer";
import { useReducer } from "react";
const Counter = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch(actions.increment())}>Increment</button>
<button onClick={() => dispatch(actions.decrement())}>Decrement</button>
<button onClick={() => dispatch(actions.reset())}>Reset</button>
<button onClick={() => dispatch(actions.set(10))}>Set counter to 10</button>
</div>
);
};
export default Counter;
```
{% /tab %} {% /tab %}
{% tab value="tsx" label="TSX" %} {% tab value="tsx" label="TSX" %}
{% snippet path="react/reducer/file-counter.tsx" language="tsx" showLineNumbers=true label="src/components/Counter.tsx" /%}
```tsx
import { initialState, actions, reducer } from "../reducers/counterReducer";
import { useReducer } from "react";
const Counter = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch(actions.increment())}>Increment</button>
<button onClick={() => dispatch(actions.decrement())}>Decrement</button>
<button onClick={() => dispatch(actions.reset())}>Reset</button>
<button onClick={() => dispatch(actions.set(10))}>Set counter to 10</button>
</div>
);
};
export default Counter;
```
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}

View File

@ -0,0 +1,22 @@
import { initialState, actions, reducer } from "../reducers/counterReducer";
import { useReducer } from "react";
const Counter = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch(actions.increment())}>Increment</button>
<button onClick={() => dispatch(actions.decrement())}>Decrement</button>
<button onClick={() => dispatch(actions.reset())}>Reset</button>
<button onClick={() => dispatch(actions.set(10))}>Set counter to 10</button>
</div>
);
};
export default Counter;

View File

@ -0,0 +1,22 @@
import { initialState, actions, reducer } from "../reducers/counterReducer";
import { useReducer } from "react";
const Counter = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch(actions.increment())}>Increment</button>
<button onClick={() => dispatch(actions.decrement())}>Decrement</button>
<button onClick={() => dispatch(actions.reset())}>Reset</button>
<button onClick={() => dispatch(actions.set(10))}>Set counter to 10</button>
</div>
);
};
export default Counter;

View File

@ -0,0 +1,30 @@
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 }),
};

View File

@ -0,0 +1,40 @@
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 }),
};

View File

@ -0,0 +1,6 @@
export const actions = {
increment: () => ({ type: CounterActionTypes.INCREMENT }),
decrement: () => ({ type: CounterActionTypes.DECREMENT }),
reset: () => ({ type: CounterActionTypes.RESET }),
set: (value) => ({ type: CounterActionTypes.SET, payload: value }),
};

View File

@ -0,0 +1,6 @@
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 }),
};

View File

@ -0,0 +1,4 @@
export const INCREMENT = "INCREMENT";
export const DECREMENT = "DECREMENT";
export const RESET = "RESET";
export const SET = "SET";

View File

@ -0,0 +1,6 @@
export const CounterActionTypes = {
INCREMENT: "INCREMENT",
DECREMENT: "DECREMENT",
RESET: "RESET",
SET: "SET",
};

View File

@ -0,0 +1,6 @@
export const enum CounterActionTypes {
INCREMENT = "INCREMENT",
DECREMENT = "DECREMENT",
RESET = "RESET",
SET = "SET",
}

View File

@ -0,0 +1,14 @@
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;
}
};

View File

@ -0,0 +1,6 @@
export type CounterAction =
| { type: CounterActionTypes.INCREMENT }
| { type: CounterActionTypes.DECREMENT }
| { type: CounterActionTypes.RESET }
| { type: CounterActionTypes.SET; payload: number };

View File

@ -0,0 +1,2 @@
dispatch(actions.increment());
dispatch(actions.set(10));

View File

@ -0,0 +1 @@
dispatch({ type: "INCREMENT" });