Le hook d’effet ou useEffect fait partie des hooks les plus utilisés. Il permet d’exécuter des effets de bord une fois que le composant est monté dans la vue. Nous verrons comment useEffect est une solution permettant de reproduire les méthodes de cycle de vie d’un composant.
Class component
Pour comprendre correctement ce hook, je vous propose de faire un voyage dans le temps et de revenir avant la version 16.8 de React. Durant l’époque pré-hook, les functionnal components n’existait pas. Les class components étaient implémentés avec une toute autre logique.
Méthode de cycle de vie d’un class component
Je vous propose de parcourir les trois méthodes principales du cycle de vie des class components de React.
🔁 1 - componentDidMount
Cette méthode du cycle de vie est appelée juste après la méthode render qui permet de transformer le code React en un élément du DOM, c’est ce qu’on appelle la phase de mount.
A cette étape, on se trouve juste à la création du composant et l’on peut faire des opérations d’initialisation tel que les appels réseau ou la gestion des states.
🔁 2 - componentDidUpdate
Cette méthode du cycle de vie est appelée juste après qu’un composant soit mis à jour dans le DOM. En React, la mise à jour du composant est principalement régit par le changement de ses states et de ses props.
La méthode componentDidUpdate prend en entrée deux paramètres :
- prevProps : les anciennes props du composant
- prevState : les anciens states du composant
Grâce à cette méthode, il est possible de détecter si une props ou un state a changé et faire un traitement en conséquence.
🔁 3 - componentWillUnmount
Cette méthode de cycle de vie est appelée juste avant que le composant soit unmount c’est à dire supprimé du DOM.
Cette méthode sert à nettoyer la mémoire et faire de l’optimisation en annulant toutes les suscriptions ou appel réseau en cours afin d’éviter d’avoir de la data zombie dans l’application.
Functionnal Component
Bienvenue de nouveau dans les temps modernes et l’époque des hooks ! Le functionnal component fait son apparition lors de la v16.8 de React (en février 2019). Ce changement apporte une nouvelle façon de programmer en React en remplaçant la POO (Programmation Orientée Objet) par le PF (Programmation Fonctionnelle).
Lors de ce changement, React a du trouver une alternative pour les méthodes de cycle de vie du functionnal component. La solution qui a été retenu est l’utilisation de useEffect.
Regardons de plus prêt ce qu’est useEffect puis comment retranscrire les méthodes de cycle de vie vu ci-dessus avec ce hook.
Fonctionnement du hook useEffect
Avant de se lancer dans l’utilisation de useEffect, prenons deux minutes pour analyser ce hook. Voici la signature de useEffect dans l’API React.
function useEffect(setup, dependencies?): void;
Paramètres :
- setup : fonction callback qui est exécutée à chaque fois qu’une valeur du tableau des dépendances change. Cette fonction peut retourner une callback appelé fonction de clean up qui sera exécutée avant le trigger du prochain useEffect ou lorsque le composant est démonté.
- dependencies (optionnal) : ce tableau est constitué d’objets qui sont des states et props utilisé dans le useEffect. Lorsque ces objets changent, React exécute la fonction callback setup. Nous verrons comment manipuler ce tableau dans la suite de l’article.
Il est possible de se représenter mentalement le useEffect comme cela : un effet qui est exécuté à chaque fois qu’une dépendance change. La fonction clean up est exécutée avant chaque exécution du useEffect. Nous verrons dans la suite les détails concernant ce comportement.
useEffect(() => {
effect
return () => {
cleanup
}
}, [dependencies])
Maintenant que nous savons à quoi ressemble un useEffect, voyons à quoi il sert en pratique.
Exemple
Pour illustrer les propos de cet article, nous prendrons l’exemple que vous pouvez retrouver sur le repository git suivant : https://github.com/Delmotte-Vincent/demo-useEffect.
Ce composant a une props color et une variable d’état lightOn qui indique si la lumière est allumée ou éteinte. Ce composant permet de modifier la couleur de la lumière et d’allumer ou éteindre l’ampoule.
Simuler le cycle de vie avec useEffect
L’une des missions de useEffect est de simuler le cycle de vie d’un functionnal component. Dans cette partie, je vais vous montrer l’équivalence des trois méthodes de cycle de vie d’un class component implémenté dans un functionnal component avec le hook useEffect.
🔁 1 - Equivalent de componentDidMount
Pour reproduire le comportement de la méthode componentDidMount (exécuter du code au mount du composant) nous utilisons un comportement du useEffect qui est : chaque fois qu’un composant est mount, quoi qu’il se passe le useEffect est exécuté une première fois. Ensuite, pour ne pas l’exécuter de nouveaux et ainsi obtenir le comportement de componentDidMount, il suffit de placer un tableau de dépendances vide.
// componentDidMount
useEffect(() => {
console.log(`👋 Trigger onMount`);
}, []);
🔁 2 - Equivalent de componentDidUpdate
Il existe plusieurs façon de reproduire le comportement de la méthode componentDidUpdate dans un functionnal component.
La première façon, est déconseillée mais existe donc nous l’abordons. Cela consiste à omettre le deuxième paramètre du useEffect. Si un useEffect ne possèdent pas de tableau de dépendance, il sera exécuté à chaque modification du composant, c’est à dire à chaque changement de props ou de state.
// componentDidUpdate
useEffect(() => {
console.log(`👋 Trigger on every changement (⚠️ not recommended)`);
});
La deuxième façon de reproduire la méthode componentDidUpdate est de passer le tableau avec la liste des dépendances pour laquelle le useEffect doit être exécuté.
Prenons comme exemple la situation suivante : vous voulez afficher la variable d’état lightOn à chaque fois que cette dernière change de valeur. Pour cela c’est simple, il suffit de passer dans le tableau des dépendances la variable lightOn.
// componentDidUpdate on state lightOn changes
useEffect(() => {
console.log(`✅ Effect => lightOn : ${lightOn ? "💡" : "⚫"}`);
}, [lightOn]);
On remarque que la callback du useEffect est appelé au mount du composant puis seulement lorsque lightOn change de valeur, si color change de valeur il n’y aura aucun message affiché dans la console.
Maintenant faisons la même chose et affichons la valeur de la props color passée au composant à chaque fois que cette dernière change.
// componentDidUpdate on props color changes
useEffect(() => {
console.log(`✅ Effect => color : ${getHeartColor()}`);
}, [color]);
De même ici, seul le changement de la props color entraine l’exécution de la callback.
Pour finir avec la méthode componentDidUpdate je vais vous montrer la combinaison des deux exemples suivants, c’est à dire, exécuter la callback sous deux conditions qui sont :
- le state lightOn change de valeur
- la props color change de valeur
// componentDidUpdate on state lightOn and props color change
useEffect(() => {
console.log(`✅ Effect => color : ${getHeartColor()} | lightOn : ${lightOn ? "💡" : "⚫"}`);
}, [lightOn, color]);
On a donc bien le message qui affiche les valeurs de lightOn et de color chaque fois que l’une d’entre elles changent de valeur.
🔁 3 - Equivalent de componentWillUnmount
Pour reproduire la méthode componentWillUnmount dans un functionnal component, vous devez passez une fonction de nettoyage que le hook useEffect appellera lorsque le composant sera unMount. Cette fonction clean up est retournée par la fonction callback du useEffect.
Le code suivant permet d’appliquer la fonction de callback lorsque le composant est unmount.
// clean up
useEffect(() => {
return () => console.log(`❌ Clean up`);
}, []);
On remarque qu’à chaque fois que le composant est unmount, la fonction de clean up est exécutée. Ainsi, grâce à ce comportement, on peut annuler tous les effets qui ne sont plus nécessaires lorsque le composant est unmount (on peut penser à un appel réseau par exemple).
Je dois vous avouer quelque chose. Je ne vous ai pas tout dit sur la fonction de clean up. Regardons l’exemple suivant :
// clean up with on state lightOn and props color change
useEffect(() => {
console.log(`✅ Effect => color : ${getHeartColor()} | lightOn : ${lightOn ? "💡" : "⚫"}`);
return () => console.log(`❌ Clean up`);
}, [lightOn, color]);
Et la vous vous dites : “La fonction de clean up n’est pas censée s’exécuter seulement lorsque le composant est unmount ? Pourquoi est-elle exécutée chaque fois qu’une valeur du tableau de dépendances changes ?”
Oui, vous ne rêvez pas, il est possible d’exécuter la fonction de clean up chaque fois que la valeur d’une des dépendances change. Pour comprendre ce fonctionnement je dois vous expliquer ce qui se passe lorsque qu’un state ou une props change.
Que ce passe-t-il en réalité lorsque la props color change?
Ordre | Action |
---|---|
1 | React détecte le changement de valeur de la props color. |
2 | React déclenche la fonction de clean up de tous les useEffect possédant color dans leur tableau de dépendances. |
3 | React procèdent à la modification du composant en mettant à jour la valeur de color, et il trigger tous les useEffect possédant color dans leur tableau de dépendances. |
Cette stratégie adoptée par React permet de nettoyer (clean up) tous les effets entre chaque rendu, laissant au développeur la possibilité d’annuler des traitements asynchrones non complétés (ex : une requête réseau). Ce processus permet d’éviter les données zombies qui peuvent potentiellement ralentir l’application.
Pour conclure, la fonction clean up permet de réaliser le rôle de la méthode de cycle de vie componentWillUnmount mais elle a également le pouvoir d’appliquer un traitement entre chaque changement de valeur du tableau de dépendances du useEffect. On peut dire que la fonction de clean up est une méthode componentWillUnmount sous stéroïde.
Conclusion
Nous avons vu dans cet article un historique de la façon de faire des composants en React, en passant des class components aux functionnal components.
Nous avons vu qu’une des principales missions du useEffect est de reproduire le comportement des méthodes du cycle de vie qui étaient présentes dans les class components et qui ont disparus avec l’avènement des functionnal components.
J’espère vous avoir éclairé sur ce hook qui est souvent utilisé en React mais qui recèle beaucoup de secret. J’espère que cet article vous a plus et vous aidera dans vos prochains défis de code !