Aller au contenu principal

Les hooks de React

Introduction

Ç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 :

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>;
}
}

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 😇

React.useEffect(() => {
// ...
}, []);

Pas cool, hein ? 😂
Et bien dans ces exemples, on a trois manières d'écrire un useEffect :

  1. Écriture #1 : le hook est exécuté une seule fois, après le premier rendu du composant
  2. Écriture #2 : le hook est exécuté à chaque mise à jour du composant
  3. Écriture #3 : le hook est exécuté à chaque mise à jour du composant, mais seulement si la propriété uneProp de props a changé
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.

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

React.useEffect(() => {
// ...
}, []);

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

React.useEffect(() => {
// ...
});

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.

React.useEffect(() => {
// ...
}, [props.uneProp]);

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 😏

React.useEffect(() => {
return () => {
// ...
};
}, []);

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 !

Counter.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>
);
};

🔢 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.

MyComponent.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>
);
};

Voici les possibilités :

Dans quel ordre les messages s'affichent-ils dans la console ?

🧩 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à 😁