Accueil / Articles / Arrêtez de recourir systématiquement à useEffect pour chaque problème en React.

Arrêtez de recourir systématiquement à useEffect pour chaque problème en React.

Les effets s’ synchronisent avec des systèmes externes ; ils sont générés lors du rendu, gèrent les clics dans les événements, et récupèrent des données réelles à l’aide d’outils spécialisés.

1424 mots

Le piège de useEffect

Les apprenants utilisant les hooks ont souvent recours à useEffect pour effectuer des requêtes, gérer des états dérivés ou répondre aux clics de bouton. Les effets s’ synchronisent avec des systèmes externes — ils ne constituent pas l’outil par défaut pour la logique de rendu ordinaire.

Ce que fait réellement useEffect

Après le rendu, React exécute les effets afin d’ajouter des écouteurs, d’ouvrir des connexions ou de communiquer de manière impérative avec un élément extérieur à React. Une utilisation incorrecte provoque des problèmes tels que des fluctuations visuelles, des requêtes redondantes ou des erreurs liées à des concurrences.

// ❌ BAD: Two renders, unnecessary state
function UserProfile({ firstName, lastName }) {
  const [fullName, setFullName] = useState('');
useEffect(() => {
    setFullName(`${firstName} ${lastName}`);
  }, [firstName, lastName]);
  return <h1>{fullName}</h1>;
}
// ✅ GOOD: Calculate during render
function UserProfile({ firstName, lastName }) {
  const fullName = `${firstName} ${lastName}`;
return <h1>{fullName}</h1>;
}
// ❌ BAD: Unnecessary effect + state
function ProductList({ products }) {
  const [sortedProducts, setSortedProducts] = useState([]);
useEffect(() => {
    const sorted = [...products].sort((a, b) => a.price - b.price);
    setSortedProducts(sorted);
  }, [products]);
  return (
    <ul>
      {sortedProducts.map(product => (
        <li key={product.id}>{product.name} - ${product.price}</li>
      ))}
    </ul>
  );
}
// ✅ GOOD: Calculate during render (useMemo for expensive ops)
function ProductList({ products }) {
  const sortedProducts = useMemo(() => {
    return [...products].sort((a, b) => a.price - b.price);
  }, [products]);
return (
    <ul>
      {sortedProducts.map(product => (
        <li key={product.id}>{product.name} - ${product.price}</li>
      ))}
    </ul>
  );
}
// ❌ BAD: Manual fetching with useEffect
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
useEffect(() => {
    let cancelled = false;
    async function fetchUser() {
      try {
        setLoading(true);
        const response = await fetch(`/api/users/${userId}`);
        if (!response.ok) throw new Error('Failed to fetch');
        const data = await response.json();
        if (!cancelled) setUser(data);
      } catch (err) {
        if (!cancelled) setError(err.message);
      } finally {
        if (!cancelled) setLoading(false);
      }
    }
    fetchUser();
    return () => { cancelled = true; };
  }, [userId]);
  if (loading) return <Spinner />;
  if (error) return <p>Error: {error}</p>;
  return <div>{user.name}</div>;
}
// ✅ GOOD: Use TanStack Query (React Query)
import { useQuery } from '@tanstack/react-query';
function UserProfile({ userId }) {
  const { data: user, isPending, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json()),
  });
  if (isPending) return <Spinner />;
  if (error) return <p>Error loading user</p>;
  return <div>{user.name}</div>;
}
// ❌ BAD: Overcomplicating with useEffect
function LikeButton({ postId }) {
  const [liked, setLiked] = useState(false);
useEffect(() => {
    if (liked) {
      fetch(`/api/posts/${postId}/like`, { method: 'POST' });
    } else {
      fetch(`/api/posts/${postId}/unlike`, { method: 'POST' });
    }
  }, [liked, postId]);
  return (
    <button onClick={() => setLiked(!liked)}>
      {liked ? '❤️' : '🤍'}
    </button>
  );
}
// ✅ GOOD: Handle in the event handler
function LikeButton({ postId }) {
  const [liked, setLiked] = useState(false);
const handleLike = async () => {
    const newLiked = !liked;
    setLiked(newLiked);
    await fetch(`/api/posts/${postId}/${newLiked ? 'like' : 'unlike'}`, {
      method: 'POST',
    });
  };
  return (
    <button onClick={handleLike}>
      {liked ? '❤️' : '🤍'}
    </button>
  );
}
function WindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);

    // Cleanup is crucial!
    return () => window.removeEventListener('resize', handleResize);
  }, []);
  return <p>Window width: {width}px</p>;
}
function ChatRoom({ roomId }) {
  const [messages, setMessages] = useState([]);
useEffect(() => {
    const connection = createWebSocketConnection(roomId);

    connection.onMessage = (msg) => {
      setMessages(prev => [...prev, msg]);
    };
    connection.connect();
    return () => {
      connection.disconnect(); // Cleanup!
    };
  }, [roomId]);
  return <MessageList messages={messages} />;
}
function ChartComponent({ data }) {
  const chartRef = useRef(null);
  const chartInstance = useRef(null);
useEffect(() => {
    if (chartRef.current && !chartInstance.current) {
      chartInstance.current = new ChartLibrary(chartRef.current, {
        type: 'line',
        data: data,
      });
    }
    return () => {
      chartInstance.current?.destroy();
      chartInstance.current = null;
    };
  }, []);
  return <canvas ref={chartRef} />;
}
function PageTitle({ title }) {
  useEffect(() => {
    document.title = `${title} | My App`;

    return () => {
      document.title = 'My App'; // Reset on unmount
    };
  }, [title]);
return null;
}

Quatre erreurs courantes

1. Calculer des valeurs dans les effets

Dérive lors du rendu (ou useMemo lorsque c’est coûteux). Les effets qui modifient setState à partir de props/état contiennent souvent des boucles de rétroaction interdites.

2. Calculs coûteux dans les effets

Gardez les calculs purs dans le rendu/memo, et non après l’affichage.

3. Récupération de données en tant qu’effet non contrôlé

La récupération de données nécessite une capacité d’annulation, du cacheage et une gestion des erreurs. Préférez des bibliothèques ou frameworks dédiés avec des mécanismes de chargement ; si vous utilisez des effets, gérez les interruptions et les conflits.

4. Actions de l’utilisateur dans les effets

Les gestionnaires de clic doivent être placés sur les événements. Les effets déclenchés par un changement de flag suite à un clic introduisent de l’indirection et des bugs.

Arbre de décision

Si le calcul provient de l’état/props existants → rendu. S’il répond à un clic/soumission → gestionnaire d’événement. S’il interagit avec une API DOM, une source réseau ou le cycle de vie d’un widget tiers → effet.

Lorsque les effets sont appropriés

Écouteurs d’événements externes

WebSockets

Initialisation de bibliothèques tierces

API non React impératives qui doivent être nettoyées correctement

Conclusion

useEffect à la synchronisation avec des systèmes extérieurs à React — et non comme outil par défaut pour chaque opération après rendu.

Les tableaux de dépendances font partie du contrat — en omettant des valeurs, des fermetures obsolètes apparaissent ; si l’on liste trop d’objets et d’effets, chaque rendu devient très lent.

Lors de la synchronisation avec les paramètres de recherche URL ou des stores externes, privilégiez des hooks conçus spécifiquement pour encapsuler les limites de l’effet.

Les applications rendues côté serveur ne doivent pas démarrer d’effets réservés au navigateur sans mécanismes de protection ; contrôlez cela en fonction de l’environnement ou du cycle de vie.

Les fonctions de nettoyage doivent désactiver les écouteurs et les temporiseurs ; des gestionnaires restants sont une source classique d’avertissements concernant les modifications de l’état après désinstallation.

Les tableaux de dépendances font partie du contrat — en omettant des valeurs, des fermetures obsolètes apparaissent ; si l’on liste trop d’objets et d’effets, chaque rendu devient très lent.

Lors de la synchronisation avec les paramètres de recherche URL ou des stores externes, privilégiez des hooks conçus spécifiquement pour encapsuler les limites de l’effet.

Les applications générées côté serveur ne doivent pas lancer d’effets réservés au navigateur sans mécanismes de protection ; il convient d’imposer des contrôles en fonction de l’environnement ou du cycle de vie.

Les fonctions de nettoyage doivent désactiver les écouteurs et les temporiseurs ; la présence de gestionnaires non supprimés est une cause fréquente d’avertissements liés à des modifications de l’état après désinstallation.

Les tableaux de dépendances font partie des exigences contractuelles — omettre des valeurs entraîne l’apparition de fermetures obsolètes ; lister trop d’objets et d’effets ralentit considérablement chaque rendu.

Lors de la synchronisation avec les paramètres de recherche URL ou des bases de données externes, privilégiez des hooks conçus spécifiquement pour encadrer les effets concernés.

Les applications générées côté serveur ne doivent pas lancer d’effets réservés au navigateur sans mécanismes de protection ; il convient d’imposer des contrôles en fonction de l’environnement ou du cycle de vie.

Les fonctions de nettoyage doivent désactiver les écouteurs et les temporiseurs ; la présence de gestionnaires non supprimés est une cause fréquente d’avertissements liés à des modifications de l’état après désinstallation.

Les tableaux de dépendances font partie du contrat — omettre des valeurs entraîne l’apparition de closures obsolètes ; lister trop d’objets et d’effets ralentit considérablement chaque rendu.

Lors de la synchronisation avec les paramètres de recherche URL ou des stores externes, privilégiez des hooks conçus spécifiquement pour encapsuler les limites des effets.

Les applications rendues côté serveur ne doivent pas lancer d’effets réservés au navigateur sans mécanismes de protection ; contrôlez cela en fonction de l’environnement ou du cycle de vie.

Les fonctions de nettoyage doivent désactiver les écouteurs et les temporiseurs ; des gestionnaires restants sont une source classique d’avertissements concernant les modifications de l’état après désinstallation.

Les tableaux de dépendances font partie du contrat — omettre des valeurs entraîne l’apparition de closures obsolètes ; lister trop d’objets et d’effets ralentit considérablement chaque rendu.

Lors de la synchronisation avec les paramètres de recherche URL ou des stores externes, privilégiez des hooks conçus spécifiquement pour encapsuler les limites des effets.

Les applications générées côté serveur ne doivent pas lancer d’effets réservés au navigateur sans mécanismes de protection ; il convient d’imposer des contrôles en fonction de l’environnement ou du cycle de vie.

Les fonctions de nettoyage doivent désactiver les écouteurs et les temporiseurs ; la présence de gestionnaires non supprimés est une cause fréquente d’avertissements liés à des modifications de l’état après désinstallation.

Les tableaux de dépendances font partie des exigences contractuelles — omettre des valeurs entraîne l’apparition de fermetures obsolètes ; lister trop d’objets et d’effets ralentit considérablement chaque rendu.

Lors de la synchronisation avec les paramètres de recherche URL ou des bases de données externes, privilégiez des hooks conçus spécifiquement pour encadrer les effets concernés.

Les applications générées côté serveur ne doivent pas lancer d’effets réservés au navigateur sans mécanismes de protection ; il convient d’imposer des contrôles en fonction de l’environnement ou du cycle de vie.

Les fonctions de nettoyage doivent désactiver les écouteurs et les temporiseurs ; la présence de gestionnaires non supprimés est une cause fréquente d’avertissements liés à des modifications de l’état après désinstallation.

Les tableaux de dépendances font partie du contrat — omettre des valeurs entraîne l’apparition de closures obsolètes ; lister trop d’objets et d’effets provoque des performances médiocres à chaque rendu.

Lors de la synchronisation avec les paramètres de recherche URL ou des stores externes, privilégiez des hooks conçus spécifiquement pour encapsuler les limites de l’effet.

Les applications rendues côté serveur ne doivent pas démarrer d’effets réservés au navigateur sans mécanismes de protection ; utilisez des contrôles basés sur l’environnement ou le cycle de vie.

Les fonctions de nettoyage doivent désactiver les écouteurs et les temporiseurs ; des gestionnaires persistants constituent une source classique d’avertissements concernant les modifications de l’état après désinstallation.