🔄 Pourquoi useEffect ?

useEffect sert à synchroniser un composant avec des effets de bord — des opérations qui ne font pas partie du rendu JSX pur : fetch de données, timers, abonnements, manipulation du DOM...

  • Appels API et fetch de données
  • Abonnements (WebSocket, EventSource)
  • Timers (setTimeout, setInterval)
  • Modification du document.title
  • Écouteurs d'événements sur window
⚠️ Ne jamais effectuer d'effets de bord directement dans le corps du composant — ils seraient exécutés à chaque rendu et pourraient créer des boucles infinies.

📝 Syntaxe de base

import { useState, useEffect } from 'react';

function Exemple() {
  const [count, setCount] = useState(0);

  // Effet sans dépendances — s'exécute à chaque rendu
  useEffect(() => {
    document.title = 'Compteur : ' + count;
  });

  // Effet avec [] — s'exécute une seule fois (montage)
  useEffect(() => {
    console.log('Composant monté');
  }, []);

  // Effet avec dépendances — s'exécute quand count change
  useEffect(() => {
    console.log('count a changé :', count);
  }, [count]);

  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

📌 Tableau de dépendances

Le deuxième argument de useEffect contrôle quand l'effet se ré-exécute.

// 1. Pas de tableau — s'exécute à CHAQUE rendu
useEffect(() => { /* ... */ });

// 2. Tableau vide [] — s'exécute UNE FOIS au montage
useEffect(() => {
  // Idéal pour : fetch initial, abonnements one-shot
}, []);

// 3. Avec dépendances — s'exécute quand une dep change
useEffect(() => {
  fetchUser(userId);
}, [userId]);  // Re-exécute si userId change

// ⚠️ ESLint eslint-plugin-react-hooks vérifie que
// toutes les valeurs utilisées dans l'effet sont dans le tableau
✅ Règle : inclure dans le tableau toutes les valeurs du composant utilisées dans l'effet (state, props, fonctions).

🧹 Nettoyage (cleanup)

L'effet peut retourner une fonction de nettoyage qui s'exécute avant le prochain effet ou au démontage du composant.

// Timer — nettoyage pour éviter les fuites mémoire
useEffect(() => {
  const intervalId = setInterval(() => {
    setTemps(t => t + 1);
  }, 1000);

  return () => clearInterval(intervalId); // cleanup
}, []);

// EventListener sur window
useEffect(() => {
  function handleResize() {
    setWidth(window.innerWidth);
  }
  window.addEventListener('resize', handleResize);
  return () => window.removeEventListener('resize', handleResize);
}, []);

// AbortController pour annuler un fetch
useEffect(() => {
  const controller = new AbortController();

  fetch('/api/data', { signal: controller.signal })
    .then(res => res.json())
    .then(data => setData(data))
    .catch(err => { if (err.name !== 'AbortError') setError(err); });

  return () => controller.abort(); // annule le fetch si le composant démonte
}, []);

🌐 Fetch de données avec useEffect

function ListeUtilisateurs() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController();

    async function fetchUsers() {
      try {
        setLoading(true);
        const res = await fetch('https://jsonplaceholder.typicode.com/users', {
          signal: controller.signal
        });
        if (!res.ok) throw new Error('Erreur réseau ' + res.status);
        const data = await res.json();
        setUsers(data);
      } catch (err) {
        if (err.name !== 'AbortError') setError(err.message);
      } finally {
        setLoading(false);
      }
    }

    fetchUsers();
    return () => controller.abort();
  }, []); // [] = une seule fois au montage

  if (loading) return <p>Chargement...</p>;
  if (error) return <p style={{ color: 'red' }}>{error}</p>;

  return (
    <ul>
      {users.map(u => <li key={u.id}>{u.name}</li>)}
    </ul>
  );
}
📝 Exercices R04 ▶ Mini-projet : Weather Widget 🧠 QCM R04 Suivant : Événements →