Débutant30 min de lecture

Effets secondaires avec useEffect

Comprends les effets secondaires dans React, maîtrise le hook useEffect avec son tableau de dépendances, et apprends les patterns courants pour récupérer des données, gérer des timers et nettoyer les ressources.

Qu'est-ce que les effets secondaires ?

Les composants React sont principalement responsables de calculer et retourner l'interface utilisateur basée sur leurs props et leur state. C'est la logique de rendu du composant — elle devrait être pure, ce qui signifie qu'elle produit le même résultat pour les mêmes entrées sans modifier quoi que ce soit en dehors d'elle-même.

Cependant, les applications réelles doivent faire plus que simplement afficher l'interface. Elles doivent :

  • Récupérer des données depuis une API
  • Établir des abonnements (par exemple, des connexions WebSocket)
  • Manipuler le DOM directement (par exemple, changer le titre du document)
  • Définir des timers avec setTimeout ou setInterval
  • Lire ou écrire dans localStorage
  • Enregistrer des événements analytics

Ces opérations sont appelées effets secondaires car elles affectent des choses en dehors du résultat de rendu du composant. Elles interagissent avec le "monde extérieur" — le navigateur, le réseau ou d'autres systèmes.

Tu ne devrais pas exécuter d'effets secondaires directement dans la phase de rendu d'un composant. Cela peut causer des bugs, des boucles infinies et un comportement imprévisible. À la place, React fournit le hook useEffect pour gérer les effets secondaires en toute sécurité.

jsx
// Mauvais : effet secondaire pendant le rendu
function BadComponent() {
  document.title = 'Hello'; // S'exécute à chaque rendu — pas idéal
  return <p>Hello</p>;
}

// Correct : effet secondaire dans useEffect
function GoodComponent() {
  React.useEffect(() => {
    document.title = 'Hello';
  }, []);
  return <p>Hello</p>;
}

Syntaxe de useEffect et tableau de dépendances

Le hook useEffect prend deux arguments :

  1. Une fonction de configuration (l'effet) qui contient ton code d'effet secondaire.
  2. Un tableau de dépendances optionnel qui contrôle quand l'effet s'exécute.
jsx
React.useEffect(() => {
  // Code d'effet secondaire ici
}, [dependencies]);

Le tableau de dépendances détermine quand l'effet se ré-exécute :

1. Tableau vide [] — s'exécute une fois au montage :

jsx
React.useEffect(() => {
  console.log('Composant monté !');
  // S'exécute une fois, après le premier rendu
}, []);

C'est l'équivalent de componentDidMount dans les composants de classe. Utilise ceci pour une configuration unique comme récupérer des données initiales ou établir un abonnement.

2. Tableau avec dépendances [a, b] — s'exécute quand les dépendances changent :

jsx
React.useEffect(() => {
  console.log('Count a changé à :', count);
  document.title = `Count: ${count}`;
}, [count]);

L'effet s'exécute après le premier rendu et chaque fois que count change. React compare la valeur actuelle de chaque dépendance avec sa valeur précédente en utilisant Object.is(). Si une dépendance a changé, l'effet s'exécute à nouveau.

3. Pas de tableau de dépendances — s'exécute après chaque rendu :

jsx
React.useEffect(() => {
  console.log('Composant rendu');
  // S'exécute après CHAQUE rendu — utilise avec parcimonie !
});

C'est rarement ce que tu veux. Sans tableau de dépendances, l'effet s'exécute après chaque rendu, ce qui peut causer des problèmes de performance ou des boucles infinies si l'effet met à jour le state.

Règles pour les dépendances :

  • Inclus chaque valeur de la portée du composant (props, state, variables) que l'effet utilise.
  • Ne mens pas sur les dépendances — si ton effet utilise count, inclus count dans le tableau.
  • Le plugin ESLint de React (react-hooks/exhaustive-deps) peut t'aider à détecter les dépendances manquantes.

Fonctions de nettoyage

Certains effets secondaires doivent être nettoyés quand le composant est démonté ou avant que l'effet ne se ré-exécute. Par exemple, si tu définis un timer, tu dois le supprimer. Si tu t'abonnes à un événement, tu dois te désabonner.

La fonction de configuration peut retourner une fonction de nettoyage. React appellera cette fonction de nettoyage :

  • Avant que l'effet ne se ré-exécute (quand les dépendances changent)
  • Quand le composant est démonté
jsx
React.useEffect(() => {
  // Configuration : démarrer un timer
  const timer = setInterval(() => {
    console.log('Tick');
  }, 1000);

  // Nettoyage : arrêter le timer
  return () => {
    clearInterval(timer);
  };
}, []); // Deps vide : configure une fois, nettoie au démontage

Écouteurs d'événements :

jsx
React.useEffect(() => {
  function handleResize() {
    console.log('Taille de la fenêtre :', window.innerWidth);
  }

  window.addEventListener('resize', handleResize);

  return () => {
    window.removeEventListener('resize', handleResize);
  };
}, []);

Pourquoi le nettoyage est important : Sans nettoyage, tu obtiens des fuites mémoire. Si un composant se monte et se démonte plusieurs fois (par exemple, en naviguant entre les pages), chaque montage créerait un nouveau timer ou écouteur sans supprimer les anciens. Avec le temps, cela dégrade les performances et cause des bugs.

Nettoyage avec dépendances :

Quand un effet a des dépendances et se ré-exécute, React appelle le nettoyage de l'effet précédent avant d'exécuter le nouveau :

jsx
React.useEffect(() => {
  const connection = createConnection(roomId);
  connection.connect();

  return () => {
    connection.disconnect(); // Déconnexion de l'ancienne salle
  };
}, [roomId]); // Se ré-exécute quand roomId change

Quand roomId change : déconnexion de l'ancienne salle (nettoyage), puis connexion à la nouvelle salle (configuration).

Cas d'usage courants

Voici les patterns les plus courants pour useEffect :

1. Mettre à jour le titre du document :

jsx
function PageTitle({ title }) {
  React.useEffect(() => {
    document.title = title;
  }, [title]);

  return <h1>{title}</h1>;
}

2. Récupérer des données au montage :

jsx
function UserProfile({ userId }) {
  const [user, setUser] = React.useState(null);

  React.useEffect(() => {
    let cancelled = false;

    async function fetchUser() {
      const res = await fetch(`/api/users/${userId}`);
      const data = await res.json();
      if (!cancelled) {
        setUser(data);
      }
    }

    fetchUser();

    return () => {
      cancelled = true; // Empêche la mise à jour du state si le composant est démonté
    };
  }, [userId]);

  if (!user) return <p>Chargement...</p>;
  return <p>{user.name}</p>;
}

Le flag cancelled empêche de définir le state après que le composant a été démonté, ce qui causerait un avertissement React.

3. Définir un timer :

jsx
function Clock() {
  const [time, setTime] = React.useState(new Date());

  React.useEffect(() => {
    const timer = setInterval(() => {
      setTime(new Date());
    }, 1000);

    return () => clearInterval(timer);
  }, []);

  return <p>{time.toLocaleTimeString()}</p>;
}

4. Écouter des événements du navigateur :

jsx
function ScrollTracker() {
  const [scrollY, setScrollY] = React.useState(0);

  React.useEffect(() => {
    const handleScroll = () => setScrollY(window.scrollY);
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, []);

  return <p>Défilé : {scrollY}px</p>;
}

5. Synchroniser avec localStorage :

jsx
function usePersistentState(key, initial) {
  const [value, setValue] = React.useState(() => {
    const saved = localStorage.getItem(key);
    return saved !== null ? JSON.parse(saved) : initial;
  });

  React.useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}

useEffect en action

html
<div id="root"></div>
<script src="https://unpkg.com/react@19/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@19/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
  const { useState, useEffect } = React;

  function App() {
    const [count, setCount] = useState(0);
    const [mounted, setMounted] = useState(false);

    // Effect that runs once on mount
    useEffect(() => {
      setMounted(true);
      document.title = 'useEffect Demo';
    }, []);

    // Effect that runs when count changes
    useEffect(() => {
      document.title = `Count: ${count}`;
    }, [count]);

    return (
      <div>
        <h2>useEffect Demo</h2>
        <p>Component mounted: {mounted ? 'Yes' : 'No'}</p>
        <p>Count: {count}</p>
        <button onClick={() => setCount(count + 1)}>Increment</button>
        <p><em>Check the browser tab title — it updates with the count!</em></p>
      </div>
    );
  }

  ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>

Que signifie un tableau de dépendances vide [] dans useEffect ?

Prêt à pratiquer ?

Crée ton compte gratuit pour accéder à l'éditeur de code interactif, lancer les défis et suivre ta progression.