feat: Add hooks examples and explanations for React component lifecycle

This commit is contained in:
Gauthier Daniels 2025-04-21 12:12:32 +02:00
parent 27caad99e6
commit 15cc58f07f
4 changed files with 267 additions and 220 deletions

View File

@ -32,7 +32,7 @@ export function QuestionIcon(props: GradientProps) {
stroke-linejoin="round"
/>
<path
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"
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"
class="fill-[var(--icon-foreground)] stroke-[color:var(--icon-foreground)]"
stroke-width={2}
stroke-linecap="round"

View File

@ -4,6 +4,9 @@ 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";
Ç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.
@ -21,75 +24,7 @@ C'était pas mal, mais ça devenait vite compliqué à gérer, notamment pour pa
Pour te donner un aperçu, voici à quoi ressemblait un composant de classe avec les trois étapes du cycle de vie :
{% tabs defaultSelectedTab="jsx" %}
{% tab value="jsx" label="JSX" %}
```jsx
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>;
}
}
```
{% /tab %}
{% tab value="tsx" label="TSX" %}
```tsx
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>;
}
}
```
{% /tab %}
{% /tabs %}
<tabs.reactClassComponent />
Comme dirait l'un de mes chers confrères jury :
@ -126,39 +61,7 @@ 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 defaultSelectedTab="1" %}
{% tab value="1" label="Écriture #1" %}
```jsx
React.useEffect(() => {
// ...
}, []);
```
{% /tab %}
{% tab value="2" label="Écriture #2" %}
```jsx
React.useEffect(() => {
// ...
}, [props.uneProp]);
```
{% /tab %}
{% tab value="3" label="Écriture #3" %}
```jsx
React.useEffect(() => {
// ...
});
```
{% /tab %}
{% /tabs %}
<tabs.reactUseEffectSyntaxes />
Pas cool, hein ? 😂
Et bien dans ces exemples, on a trois manières d'écrire un useEffect :
@ -167,11 +70,9 @@ Et bien dans ces exemples, on a trois manières d'écrire un useEffect :
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 %}
<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.
@ -183,31 +84,19 @@ Selon ce tableau, le hook sera exécuté à des moments différents du cycle de
### ⚙️ ComponentDidMount
```jsx
React.useEffect(() => {
// ...
}, []);
```
<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
```jsx
React.useEffect(() => {
// ...
});
```
<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.
```jsx
React.useEffect(() => {
// ...
}, [props.uneProp]);
```
<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é.
@ -217,14 +106,7 @@ Le hook sera exécuté à chaque mise à jour du composant _(ainsi qu'au montage
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 😏
```jsx
React.useEffect(() => {
// ...
return () => {
// ...
};
}, []);
```
<tabs.reactUseEffectUnmount />
Tu as vu ce petit `return` ? Et bien, c'est notre équivalent de `componentWillUnmount` pour les composants de classe !
@ -242,65 +124,9 @@ Pour éviter que React se dise "Tiens, il y a eu un changement, je vais re-rendr
Allez, mettons un peu ce qu'on voit de voir en pratique !
{% tabs defaultSelectedTab="jsx" %}
Voici un exemple de code qui utilise `useEffect` pour gérer le cycle de vie d'un composant :
{% tab value="jsx" label="JSX" %}
```jsx
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>;
};
```
{% /tab %}
{% tab value="tsx" label="TSX" %}
```tsx
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>;
};
```
{% /tab %}
{% /tabs %}
<tabs.reactUseEffectExample />
### 🔢 On revient sur le cycle de vie !
@ -309,39 +135,16 @@ On revient sur le cycle de vie d'un composant maintenant qu'on a vu `useEffect`
Je vais te donner un exemple de code supplémentaire et tu vas devoir deviner l'ordre d'apparition des messages dans la console.
```jsx
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>;
};
```
<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 %}
<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

View File

@ -0,0 +1,244 @@
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

@ -90,7 +90,7 @@ 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
### ⚙️ 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)_.