Contactez-nous

Le tableau des dépendances : Contrôler l'exécution de l'effet

Apprenez à utiliser le tableau de dépendances (dependency array) de useEffect pour contrôler quand vos effets de bord s'exécutent et optimiser les performances.

Le problème de l'exécution systématique

Comme nous l'avons vu, si vous ne fournissez pas de second argument à `useEffect`, la fonction d'effet s'exécute après chaque rendu du composant. Bien que simple, ce comportement est souvent sous-optimal, voire incorrect, pour plusieurs raisons :

  • Performance : Si l'effet effectue une opération coûteuse (comme une requête réseau), l'exécuter à chaque rendu (même si les données nécessaires n'ont pas changé) gaspille des ressources et peut ralentir l'application.
  • Logique incorrecte : Parfois, un effet ne doit s'exécuter qu'une seule fois (au montage du composant) ou seulement lorsque certaines données spécifiques changent. Le déclencher à chaque rendu peut entraîner des comportements non désirés (par exemple, ajouter plusieurs fois le même écouteur d'événement).
  • Boucles infinies : Si un effet met à jour l'état, et que cet état est utilisé (directement ou indirectement) par le même composant, l'exécution de l'effet après chaque rendu peut déclencher une nouvelle mise à jour, qui déclenche un nouveau rendu, qui déclenche à nouveau l'effet, créant une boucle infinie.

Pour éviter ces problèmes, React nous donne un contrôle fin sur le moment où l'effet doit être ré-exécuté grâce au tableau de dépendances.

Le second argument : Le tableau de dépendances

Le hook `useEffect` accepte un second argument optionnel : un tableau. Ce tableau, appelé tableau de dépendances, contient une liste de valeurs (généralement des props, des états, ou des fonctions définies dans le composant) que l'effet utilise et dont il dépend.

useEffect(() => {
  // Code de l'effet...
  console.log(`La valeur de 'maDependance' est : ${maDependance}`);
}, [maDependance]); // <-- Le tableau de dépendances

Le fonctionnement est le suivant :

  • Après le premier rendu, l'effet s'exécute toujours. React mémorise les valeurs des dépendances spécifiées dans le tableau.
  • Lors des rendus suivants, React compare les valeurs actuelles des dépendances dans le tableau avec les valeurs mémorisées lors de la dernière exécution de l'effet.
  • Si aucune des valeurs dans le tableau de dépendances n'a changé entre les deux rendus, React saute l'exécution de l'effet.
  • Si au moins une des valeurs dans le tableau a changé (comparaison `Object.is`), React exécute à nouveau l'effet et mémorise les nouvelles valeurs des dépendances.

Cas courants d'utilisation du tableau de dépendances

1. Exécution unique au montage (similaire à `componentDidMount`)

Si vous voulez qu'un effet ne s'exécute qu'une seule fois, juste après le montage initial du composant, vous pouvez passer un tableau de dépendances vide (`[]`).

useEffect(() => {
  // Ce code ne s'exécute qu'une seule fois, après le premier rendu.
  console.log('Le composant est monté !');
  fetchInitialData(); // Exemple: requête API initiale
  const subscription = setupSubscription(); // Exemple: mise en place d'un abonnement

  // Important: Penser au nettoyage si nécessaire (voir section suivante)
  return () => {
    console.log('Nettoyage avant démontage...');
    cancelSubscription(subscription);
  };
}, []); // <-- Tableau vide = pas de dépendances externes

Puisque le tableau est vide, les dépendances ne changeront jamais, donc React n'exécutera l'effet qu'après le premier rendu.

2. Exécution lors du changement de certaines valeurs

C'est le cas le plus fréquent. Vous spécifiez les props ou les états dont dépend votre effet. L'effet sera ré-exécuté si (et seulement si) l'une de ces valeurs change.

function UserProfile({ userId }) { // userId est une prop
  const [user, setUser] = useState(null);

  useEffect(() => {
    // Cet effet dépend de `userId`. Si `userId` change, il faut refaire la requête.
    console.log(`Récupération des données pour l'utilisateur ${userId}...`);
    fetch(`/api/users/${userId}`)
      .then(response => response.json())
      .then(data => setUser(data));

    // Note: gestion des erreurs et du nettoyage (annulation requête) omise pour la simplicité
  }, [userId]); // <-- Dépendance : l'effet se ré-exécute si userId change

  if (!user) {
    return 
Chargement...
; } return
Nom: {user.name}
; }

Ici, l'effet de récupération des données ne sera ré-exécuté que si la prop `userId` change. Si le composant `UserProfile` est re-rendu pour une autre raison (par exemple, un état interne non lié change), mais que `userId` reste identique, l'effet sera ignoré, évitant un appel API inutile.

L'importance d'être exhaustif et honnête

La règle la plus importante concernant le tableau de dépendances est : vous devez inclure toutes les valeurs (props, state, fonctions définies dans le composant) qui sont utilisées à l'intérieur de la fonction `useEffect` et qui sont susceptibles de changer au fil du temps.

Omettre une dépendance peut entraîner des bugs difficiles à déceler, notamment liés aux "closures" JavaScript. L'effet pourrait capturer une valeur obsolète (stale) d'une variable ou d'une fonction, car il ne serait pas ré-exécuté lorsque cette valeur change réellement. Par exemple, si notre effet `UserProfile` utilisait une autre prop `apiUrl` mais qu'on oubliait de l'ajouter aux dépendances :

// !!! MAUVAIS : Dépendance 'apiUrl' manquante
useEffect(() => {
  fetch(`${apiUrl}/users/${userId}`) // Utilise apiUrl ET userId
    // ... 
}, [userId]); // Oubli de apiUrl !

Si `apiUrl` changeait, l'effet ne serait pas ré-exécuté et continuerait d'utiliser l'ancienne URL, conduisant à un comportement incorrect.

Heureusement, l'équipe React fournit un plugin ESLint (`eslint-plugin-react-hooks`) avec une règle (`react-hooks/exhaustive-deps`) qui analyse votre code `useEffect` et vous avertit (souvent avec une suggestion de correction automatique) si vous avez oublié des dépendances. Il est fortement recommandé d'activer et de respecter cette règle.

En conclusion, le tableau de dépendances est l'outil essentiel pour contrôler le cycle de vie de vos effets de bord, optimiser les performances et garantir la correction logique de vos composants en s'assurant que les effets se synchronisent correctement avec les changements de données dont ils dépendent.