codelynx.dev
🇫🇷🇬🇧

Retour 09/10/2024

Quand utiliser useEffect - Le guide complet

Écris par Melvyn Malherbe le 09/10/2024


useEffect est très souvent mal utilisé et mal documenté sur internet. Aujourd'hui je vais t'expliquer précisément quand et pourquoi il faut l'utiliser. Je vais te faire comprendre une bonne fois pour toutes à quoi sert le useEffect.

(spoiler, il ne doit généralement pas être utilisé pour faire des fetch, il y a bien mieux à faire.)

Quelle est l'utilité du useEffect ?

Le useEffect a comme rôle de venir synchroniser ton composant avec des éléments externes à React. Un bon autre nom pour le useEffect aurait été useSideEffect, il permet de gérer les effets de bord de ton composant.

C'est quoi un side effect ?

Un side effect ou effet de bord est une action qui doit être effectuée en dehors de React. React gère beaucoup de choses, comme les événements DOM, les states, le rendu de ton application, mais il y a certaines choses qu'il ne gère pas.

Le useEffect permet de synchroniser ton component avec des éléments externes.

Je pourrais te citer quelques exemples de synchronisation :

  • Des Web Sockets
  • Le DOM avec des event listeners
  • Des Backends Externes
  • Des Librairies externes

On peut faire un exemple pour un composant qui se synchroniserait avec la position de la souris :

Tu peux voir qu'ici, on vient utiliser le useEffect pour venir écouter les événements de document pour mettre à jour notre state.

Pourquoi synchroniser ?

Il faut bien comprendre comment est constitué un useEffect :

JSX
useEffect(() => {
  // 1. Run Effect
  
  return () => {
    // 2. CleanUp Effect
  }
}, [/* 3. Dependencies */])

Il a vraiment ces 3 parties qui fonctionnent ensemble.

  1. Run Effect : C'est la partie qui va être exécutée à chaque fois que le component est monté ou que les dépendances changent.
  2. CleanUp Effect : C'est la partie qui va être exécutée à chaque fois que le component est démonté.
  3. Dependencies : C'est la partie qui va permettre de dire à React quand il doit exécuter le Run Effect et le CleanUp Effect.

S'il y a cleanup effect c'est qu'aucune synchronisation ne vient sans la présence d'un cleanup qui répond à la question :

Que faire quand notre composant n'est PLUS synchronisé ?

C'est justement le rôle que va avoir notre CleanUp Effect.

En règle générale, chaque effet doit être accompagné d'un CleanUp Effect qui fait qu'on cleanup notre effect.

Quand le cleanup est-il appelé ?

Il va y avoir vraiment 3 phases différentes pour notre useEffect :

  1. Mount : C'est la première fois que le composant est monté. On va exécuter le useEffect dans tous les cas
  2. Update : C'est quand le composant est mis à jour. On va exécuter le useEffect seulement si les dépendances changent
  3. Unmount : C'est quand le composant est démonté. On va exécuter le Cleanup seulement si le composant est démonté

Le Unmount, tu le comprends, c'est quand notre composant n'est plus affiché dans le DOM.

L'update par contre, c'est quand notre effet est mis à jour via le tableau de dépendances, celui-ci va venir appeler le cleanup effect puis l'effet suivant dans cet ordre !

Tu peux voir dans ce playground que dans l'ordre :

  1. Le Render est appelé
  2. Le Clean Up est appelé
  3. Le Run Effect est appelé

C'est toujours le dernier cleanup qui est appelé, tu peux t'en rendre compte car je log toujours le compteur.

Tu vas avoir ce genre de log :

Render : 2
Cleanup Effect : 1
Run Effect : 2

On commence par rendre le composant avec le compteur à 2, puis on va appeler le Cleanup Effect pour l'état précédent, donc le 1 et ensuite on appelle le Run Effect qui va être exécuté avec la dernière version du compteur, donc 2.

useEffect pour FETCH des données

J'ai dit au début de l'article que le useEffect n'était pas fait pour fetch des données, c'est partiellement faux.

Dans un monde où aucune librairie n'existe, useEffect est bien fait pour fetch des données.

Le problème c'est qu'il n'est pas optimisé et il peut créer des erreurs. La majorité des gens font ce genre de code :

Ce genre de code fonctionne, mais il n'est pas optimisé... pourquoi ? Car il n'y a pas de cleanup. Or chaque side effect doit avoir un cleanup.

De plus, on ne gère pas l'état d'erreur ou de chargement. Voici un code "propre" :

Ici on a tous les éléments qu'il faut :

  • Un AbortController qui va permettre de stopper le fetch
  • Un état de chargement qui va permettre de faire un loading
  • Un état d'erreur qui va permettre de faire un message d'erreur

Mais tu vois que le code est chiant et long ! Alors qu'avec SWR on peut faire :

Tu te rends compte de la différence de code ? Il y a littéralement 3x moins de code, alors je te demande : pourquoi utiliser le useEffect ? dans ce cas ?

Quand NE PAS utiliser le useEffect

Il ne faut surtout PAS utiliser le useEffect pour venir gérer des choses qu'on pourrait gérer avec des events. Regarde ce code :

JSX
const CounterButton = () => {
  const [count, setCount] = useState(0);

  const incrementCount = () => {
    setCount(count + 1);
  };

  useEffect(() => {
    notifyAnalytics(count);
  }, [count]);

  return (
    <div>
      <button onClick={() => incrementCount()}>Increment</button>
      <p>Current Count: {count}</p>
    </div>
  );
};

Ici le useEffect est utilisé comme une sorte de "quand count change, appel cette méthode" mais c'est le pire moyen de faire ça ! Ici au lieu d'avoir un "side effect" on veut juste un "fire event" :

JSX
const CounterButton = () => {
  const [count, setCount] = useState(0);

  const incrementCount = () => {
    const newCount = count + 1;
    setCount(newCount);
    notifyAnalytics(newCount);
  };

  return (
    <div>
      <button onClick={() => incrementCount()}>Increment</button>
      <p>Current Count: {count}</p>
    </div>
  );
};

Et voilà, on a le même code mais ici on n'a pas besoin d'un useEffect.

Autre exemple, c'est celui de venir "écouter" les props d'un composant :

JSX
const CounterButton = ({ lastName }) => {
  const [fullName, setFullName] = useState(`Melvyn + ${lastName}`);

  useEffect(() => {
    setFullName(`Melvyn + ${lastName}`);
  }, [lastName]);

  return (
    <div>
      {fullName}
    </div>
  );
};

Quand tu fais ça, c'est un peu inutile déjà car comme tu le vois, on n'utilise jamais setFullName. Ce qu'il te faut c'est faire un derived state :

JSX
const CounterButton = ({ lastName }) => {
  const fullName = `Melvyn + ${lastName}`;

  return (
    <div>
      {fullName}
    </div>
  );
};

ici on a le même résultat sans avoir besoin d'un useEffect en plus.

Conclusion, quand utiliser le useEffect ?

Pour conclure, le useEffect est très utile pour "s'abonner" à des événements du DOM, comme on l'a vu un hook useMousePosition fait un très bon usage du useEffect :

JSX
export const useMousePosition = () => {
  const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });

  useEffect(() => {
    const handleMouseMove = (event: MouseEvent) => {
      setMousePosition({ x: event.clientX, y: event.clientY });
    };

    window.addEventListener('mousemove', handleMouseMove);

    return () => {
      window.removeEventListener('mousemove', handleMouseMove);
    };
  }, []);

  return mousePosition;
};

Si tu veux t'aider pour ta décision tu peux te répondre à ces questions, si la réponse est toujours "oui" alors tu peux utiliser le useEffect.

QuestionRéponse
Ce useEffect a-t-il besoin d'un "cleanup" ?-
Est-ce que le useEffect ne PEUT PAS être remplacé par un simple "fire event" ?-
Est-ce que mon useEffect est bien en train de synchroniser avec un élément en dehors de React ?-

Voilà, si ça t'a plu tu devrais vraiment réfléchir à te former en React avec moi via mes cours gratuits :

Le meilleur moyen d'apprendre React !

Rejoins par développeurs, cette formation reçoit une note de 4.7 / 5 🚀

Reçois 12 leçons premium pour maîtriser React et faire partie des meilleurs

Du même style, je t'invite à aller regarder comment utiliser useRef, les formulaires en React ou le Guide de flexbox en CSS qui pourrait vraiment t'intéresser.

BeginReact

Cours React gratuit

Accède à des exercices, des vidéos et bien plus sur React dans la formation "BeginReact" 👇