useEffect est un Hook React qui vous permet de synchroniser un composant React avec un système extérieur.

useEffect(setup, dependencies?)

Référence

useEffect(setup, dependencies?)

Appelez useEffect à la racine de votre composant pour déclarer un Effet :

import { useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}

Voir d’autres exemples ci-dessous.

Paramètres

  • setup : la fonction contenant la logique de votre Effet. Votre fonction de mise en place peut par ailleurs renvoyer une fonction de nettoyage. Quand votre composant sera ajouté au DOM, React exécutera votre fonction de mise en place. Après chaque nouveau rendu dont les dépendances ont changé, React commencera par exécuter votre fonction de nettoyage (si vous en avez fourni une) avec les anciennes valeurs, puis exécutera votre fonction de mise en place avec les nouvelles valeurs. Une fois votre composant retiré du DOM, React exécutera votre fonction de nettoyage une dernière fois.

  • dependencies optionnelles : la liste des valeurs réactives référencées par le code de setup. Les valeurs réactives comprennent les props, les variables d’état et toutes les variables et fonctions déclarées localement dans le corps de votre composant. Si votre linter est configuré pour React, il vérifiera que chaque valeur réactive concernée est bien spécifiée comme dépendance. La liste des dépendances doit avoir un nombre constant d’éléments et utiliser un littéral défini à la volée, du genre [dep1, dep2, dep3]. React comparera chaque dépendance à sa valeur précédente au moyen de la comparaison Object.is. Si vous omettez cet argument, votre Effet sera re-exécuté après chaque rendu du composant. Découvrez la différence entre passer un tableau de dépendances, un tableau vide ou aucun tableau.

Valeur renvoyée

useEffect renvoie undefined.

Limitations

  • useEffect est un Hook, vous pouvez donc uniquement l’appeler à la racine de votre composant ou de vos propres Hooks. Vous ne pouvez pas l’appeler à l’intérieur de boucles ou de conditions. Si nécessaire, extrayez un nouveau composant et déplacez l’Effet dans celui-ci.

  • Si vous ne cherchez pas à synchroniser avec un système extérieur, c’est que vous n’avez probablement pas besoin d’un Effet.

  • Quand le Mode Strict est activé, React appellera une fois de plus votre cycle mise en place + nettoyage, uniquement en développement, avant la première mise en place réelle. C’est une mise à l’épreuve pour vérifier que votre logique de nettoyage reflète bien votre logique de mise en place, et décommissionne ou défait toute la mise en place effectuée. Si ça entraîne des problèmes, écrivez une fonction de nettoyage.

  • Si certaines de vos dépendances sont des objets ou fonctions définies au sein de votre composant, il existe un risque qu’elles entraînent des exécutions superflues de votre Effet. Pour corriger ça, retirez les dépendances superflues sur des objets et fonctions. Vous pouvez aussi extraire les mises à jour d’état et la logique non réactive hors de votre Effet.

  • Si votre Effet ne découlait pas d’une interaction (telle qu’un clic), React laissera généralement le navigateur rafraîchir l’affichage à l’écran avant d’exécuter votre Effet. Si votre Effet a des aspects visuels (par exemple, il positionne une infobulle) et que le retard est perceptible (par exemple, l’affichage vacille), remplacez useEffect par useLayoutEffect.

  • Même si votre Effet est déclenché par une interaction (telle qu’un clic), le navigateur est susceptible de rafraîchir l’affichage avant d’avoir traité les mises à jour d’état au sein de votre Effet. C’est généralement ce que vous souhaitez. Cependant, si vous devez empêcher le navigateur de rafraîchir l’affichage tout de suite, remplacez useEffect par useLayoutEffect.

  • Les Effets ne sont exécutés que côté client. Ils sont ignorés lors du rendu côté serveur.


Utilisation

Se connecter à un système extérieur

Certains composants ont besoin de rester connectés au réseau, ou à des API du navigateur, ou à des bibliothèques tierces, tout le temps qu’ils sont à l’écran. Ces systèmes ne sont pas gérés par React, on les qualifie donc de systèmes extérieurs.

Afin de connecter votre composant à un système extérieur, appelez useEffect au niveau racine de votre fonction composant :

import { useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}

Vous devez passer deux arguments à useEffect :

  1. Une fonction de mise en place avec du code de mise en place qui vous connecte au système.
    • Elle devrait renvoyer une fonction de nettoyage avec du code de nettoyage qui vous déconnecte du système.
  2. Une liste de dépendances comprenant chaque valeur issue de votre composant que ces fonctions utilisent.

React appellera vos fonctions de mise en place et de nettoyage chaque fois que nécessaire, ce qui peut survenir plusieurs fois :

  1. Votre code de mise en place est exécuté quand votre composant est ajouté à la page (montage).
  2. Après chaque nouveau rendu de votre composant, si les dépendances ont changé :
    • D’abord, votre code de nettoyage est exécuté avec les anciennes props et valeurs d’états.
    • Ensuite, votre code de mise en place est exécuté avec les nouvelles props et valeurs d’états.
  3. Votre code de nettoyage est exécuté une dernière fois lorsque votre composant est retiré de l’arborescence de la page (démontage).

Illustrons cette séquence pour l’exemple précédent.

Lorsque le composant ChatRoom ci-dessus sera ajouté à la page, il se connectera au salon de discussion en utilisant les valeurs initiales de serverUrl et roomId. Si l’une ou l’autre de ces deux valeurs change suite à un nouveau rendu (peut-être l’utilisateur a-t-il choisi un autre salon dans la liste déroulante), votre Effet se déconnectera du salon précédent, puis se connectera au nouveau salon. Lorsque le composant ChatRoom sera retiré de la page, votre Effet se déconnectera une dernière fois.

Pour vous aider à repérer des bugs, en développement React exécutera un premier cycle de mise en place et de nettoyage, avant d’exécuter la mise en place nominale. C’est une mise à l’épreuve pour vérifier que la logique de votre Effet est implémentée correctement. Si ça entraîne des problèmes, c’est que votre code de nettoyage est manquant ou incomplet. La fonction de nettoyage devrait arrêter ou défaire ce que la fonction de mise en place a initié. La règle à suivre est simple : l’utilisateur ne devrait pas pouvoir faire la différence entre une exécution unique de la mise en place (comme en production) et une séquence mise en placenettoyagemise en place (comme en développement). Explorez les solutions courantes.

Essayez d’écrire chaque Effet comme un processus autonome et de réfléchir à un seul cycle de mise en place / nettoyage à la fois. Le fait que votre composant soit en train d’être monté, de se mettre à jour ou d’être démonté ne devrait avoir aucune importance. Lorsque votre logique de nettoyage reflète correctement celle de mise en place, votre Effet n’a aucun problème avec des exécutions multiples de ses codes de mise en place et de nettoyage.

Remarque

Un Effet vous permet de garder votre composant synchronisé avec un système extérieur (tel qu’un service de discussion). Dans ce contexte, système extérieur désigne n’importe quel bout de code qui n’est pas géré par React, par exemple :

Si vous ne vous connectez pas à un système extérieur, vous n’avez sans doute pas besoin d’un Effet.

Exemples de connexion à un système extérieur

Exemple 1 sur 5 ·
Se connecter à un serveur de discussion

Dans cet exemple, le composant ChatRoom utilise un Effet pour rester connecté à un système extérieur défini dans chat.js. Appuyez sur « Ouvrir le salon » pour que le composant ChatRoom apparaisse. Ce bac à sable est en mode développement, il y aura donc un cycle supplémentaire de connexion-déconnexion, comme expliqué ici. Essayez de changer roomId et serverUrl en utilisant la liste déroulante et le champ de saisie, et voyez comme l’Effet se reconnecte au salon. Appuyez sur « Fermer le salon » pour vous déconnecter une dernière fois.

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, [roomId, serverUrl]);

  return (
    <>
      <label>
        URL du serveur :{' '}
        <input
          value={serverUrl}
          onChange={e => setServerUrl(e.target.value)}
        />
      </label>
      <h1>Bienvenue dans le salon {roomId} !</h1>
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  const [show, setShow] = useState(false);
  return (
    <>
      <label>
        Choisissez le salon de discussion :{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">Général</option>
          <option value="travel">Voyage</option>
          <option value="music">Musique</option>
        </select>
      </label>
      <button onClick={() => setShow(!show)}>
        {show ? 'Fermer le salon' : 'Ouvrir le salon'}
      </button>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId} />}
    </>
  );
}


Enrober vos Effets dans des Hooks personnalisés

Les Effets sont une « échappatoire » : vous vous en servez pour « sortir de React », et lorsqu’il n’y a pas de meilleure solution disponible pour votre cas de figure. Si vous vous retrouvez à souvent écrire manuellement des Effets, c’est généralement le signe que vous devriez en extraire certains sous forme de Hooks personnalisés pour les comportements courants dont vous équipez vos composants.

Par exemple, ce Hook personnalisé useChatRoom « masque » toute la logique de votre Effet derrière une API plus déclarative.

function useChatRoom({ serverUrl, roomId }) {
useEffect(() => {
const options = {
serverUrl: serverUrl,
roomId: roomId
};
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId, serverUrl]);
}

Vous pouvez dès lors l’utiliser dans n’importe quel composant, comme ceci :

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');

useChatRoom({
roomId: roomId,
serverUrl: serverUrl
});
// ...

L’écosystème React propose de nombreux excellents Hooks personnalisés pour tous les besoins.

Apprenez à enrober vos Effets dans des Hooks personnalisés.

Exemples d’enrobage d’Effets sous forme de Hooks personnalisés

Exemple 1 sur 3 ·
Hook useChatRoom personnalisé

Cet exemple est identique à un des exemples précédents, mais sa logique est extraite dans un Hook personnalisé.

import { useState } from 'react';
import { useChatRoom } from './useChatRoom.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

  useChatRoom({
    roomId: roomId,
    serverUrl: serverUrl
  });

  return (
    <>
      <label>
        URL du serveur :{' '}
        <input
          value={serverUrl}
          onChange={e => setServerUrl(e.target.value)}
        />
      </label>
      <h1>Bienvenue dans le salon {roomId} !</h1>
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  const [show, setShow] = useState(false);
  return (
    <>
      <label>
        Choisissez le salon de discussion :{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">Général</option>
          <option value="travel">Voyage</option>
          <option value="music">Musique</option>
        </select>
      </label>
      <button onClick={() => setShow(!show)}>
        {show ? 'Fermer le salon' : 'Ouvrir le salon'}
      </button>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId} />}
    </>
  );
}


Contrôler un widget non géré par React

Il peut arriver que vous souhaitiez garder un système extérieur synchronisé avec la valeur d’une prop ou d’un état de votre composant.

Imaginons par exemple que vous ayez un widget tiers de cartographie, ou un composant de lecture vidéo écrit sans React ; vous pouvez utiliser un Effet pour en appeler les méthodes afin que son état soit raccord avec l’état local de votre composant React. L’Effet ci-dessous crée une instance de la classe MapWidget définie dans map-widget.js. Lorsque vous modifiez la prop zoomLevel du composant Map, l’Effet appelle la méthode setZoom() sur l’instance pour la garder synchronisée !

import { useRef, useEffect } from 'react';
import { MapWidget } from './map-widget.js';

export default function Map({ zoomLevel }) {
  const containerRef = useRef(null);
  const mapRef = useRef(null);

  useEffect(() => {
    if (mapRef.current === null) {
      mapRef.current = new MapWidget(containerRef.current);
    }

    const map = mapRef.current;
    map.setZoom(zoomLevel);
  }, [zoomLevel]);

  return (
    <div
      style={{ width: 200, height: 200 }}
      ref={containerRef}
    />
  );
}

Dans cet exemple, nous n’avons pas besoin d’une fonction de nettoyage parce que la classe MapWidget ne gère que le nœud DOM qui lui a été passé. Après que le composant React Map aura été retiré de l’arborescence, tant le nœud DOM que l’instance de MapWidget seront automatiquement nettoyés par le garbage collector du moteur JavaScript du navigateur.


Charger des données avec les Effets

Vous pouvez utiliser un Effet pour charger des données pour votre composant. Remarquez que si vous utilisez un framework, il sera nettement préférable d’utiliser les mécanismes de chargement de données de votre framework plutôt que le faire manuellement dans des Effets, notamment pour des questions de performances.

Si vous souhaitez charger des données manuellement depuis votre Effet, votre code ressemblera à ceci :

import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';

export default function Page() {
const [person, setPerson] = useState('Alice');
const [bio, setBio] = useState(null);

useEffect(() => {
let ignore = false;
setBio(null);
fetchBio(person).then(result => {
if (!ignore) {
setBio(result);
}
});
return () => {
ignore = true;
};
}, [person]);

// ...

Remarquez la variable ignore, qui est initialisée à false mais mise à true lors du nettoyage. Ça garantit que votre code ne souffrira pas de “race conditions” : les réponses réseau pourraient arriver dans un ordre différent de celui de vos envois de requêtes.

import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';

export default function Page() {
  const [person, setPerson] = useState('Alice');
  const [bio, setBio] = useState(null);
  useEffect(() => {
    let ignore = false;
    setBio(null);
    fetchBio(person).then(result => {
      if (!ignore) {
        setBio(result);
      }
    });
    return () => {
      ignore = true;
    }
  }, [person]);

  return (
    <>
      <select value={person} onChange={e => {
        setPerson(e.target.value);
      }}>
        <option value="Alice">Alice</option>
        <option value="Bob">Bob</option>
        <option value="Clara">Clara</option>
      </select>
      <hr />
      <p><i>{bio ?? 'Chargement...'}</i></p>
    </>
  );
}

Vous pouvez aussi le réécrire en utilisant la syntaxe async / await, mais il vous faudra quand même une fonction de nettoyage :

import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';

export default function Page() {
  const [person, setPerson] = useState('Alice');
  const [bio, setBio] = useState(null);
  useEffect(() => {
    async function startFetching() {
      setBio(null);
      const result = await fetchBio(person);
      if (!ignore) {
        setBio(result);
      }
    }

    let ignore = false;
    startFetching();
    return () => {
      ignore = true;
    }
  }, [person]);

  return (
    <>
      <select value={person} onChange={e => {
        setPerson(e.target.value);
      }}>
        <option value="Alice">Alice</option>
        <option value="Bob">Bob</option>
        <option value="Clara">Clara</option>
      </select>
      <hr />
      <p><i>{bio ?? 'Chargement...'}</i></p>
    </>
  );
}

Implémenter le chargement de données directement dans les Effets devient vite répétitif et complexifie l’ajout ultérieur d’optimisations telles que la mise en cache ou le rendu côté serveur. Il est plus facile d’utiliser un Hook personnalisé — qu’il soit de vous ou maintenu par la communauté.

En détail

Que préférer au chargement de données dans les Effets ?

Écrire nos appels fetch dans les Effets constitue une façon populaire de charger des données, en particulier pour des applications entièrement côté client. Il s’agit toutefois d’une approche de bas niveau qui comporte plusieurs inconvénients significatifs :

  • Les Effets ne fonctionnent pas côté serveur. Ça implique que le HTML rendu côté serveur avec React proposera un état initial sans données chargées. Le poste client devra télécharger tout le JavaScript et afficher l’appli pour découvrir seulement alors qu’il lui faut aussi charger des données. Ce n’est pas très efficace.
  • Charger depuis les Effets entraîne souvent des « cascades réseau ». On affiche le composant parent, il charge ses données, affiche ses composants enfants, qui commencent seulement alors à charger leurs propres données. Si le réseau n’est pas ultra-rapide, cette séquence est nettement plus lente que le chargement parallèle de toutes les données concernées.
  • Charger depuis les Effets implique généralement l’absence de pré-chargement ou de cache des données. Par exemple, si le composant est démonté puis remonté, il lui faudrait charger à nouveau les données dont il a besoin.
  • L’ergonomie n’est pas top. Écrire ce genre d’appels fetch manuels nécessite pas mal de code générique, surtout lorsqu’on veut éviter des bugs tels que les race conditions.

Cette liste d’inconvénients n’est d’ailleurs pas spécifique à React. Elle s’applique au chargement de données lors du montage quelle que soit la bibliothèque. Comme pour le routage, bien orchestrer son chargement de données est un exercice délicat, c’est pourquoi nous vous recommandons plutôt les approches suivantes :

  • Si vous utilisez un framework, utilisez son mécanisme intégré de chargement de données. Les frameworks React modernes ont intégré le chargement de données de façon efficace afin d’éviter ce type d’ornières.
  • Dans le cas contraire, envisagez l’utilisation ou la construction d’un cache côté client. Les solutions open-source les plus populaires incluent React Query, useSWR, et React Router 6.4+. Vous pouvez aussi construire votre propre solution, auquel cas vous utiliseriez sans doute les Effets sous le capot, mais ajouteriez la logique nécessaire au dédoublonnement de requêtes, à la mise en cache des réponses, et à l’optimisation des cascades réseau (en préchargeant les données ou en consolidant vers le haut les besoins de données des routes).

Vous pouvez continuer à charger les données directement dans les Effets si aucune de ces approches ne vous convient.


Spécifier les dépendances réactives

Remarquez bien que vous ne pouvez pas « choisir » les dépendances de votre Effet. Chaque valeur réactive utilisée par le code de votre Effet doit être déclarée dans votre liste de dépendances, laquelle découle donc du code environnant :

function ChatRoom({ roomId }) { // C’est une valeur réactive
const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // Ça aussi

useEffect(() => {
const connection = createConnection(serverUrl, roomId); // Cet Effet lit ces valeurs réactives
connection.connect();
return () => connection.disconnect();
}, [serverUrl, roomId]); // ✅ Vous devez donc les lister comme dépendances de votre Effet
// ...
}

Si serverUrl ou roomId change, votre Effet se reconnectera à la discussion en utilisant leurs valeurs à jour.

Les valeurs réactives comprennent les props et toutes les variables et fonctions déclarées directement au sein de votre composant. Dans la mesure où roomId et serverUrlsont des valeurs réactives, vous ne pouvez pas les retirer de la liste des dépendances. Si vous tentiez de les retirer et que votre linter est correctement configuré pour React, il vous l’interdirait :

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, []); // 🔴 React Hook useEffect has missing dependencies: 'roomId' and 'serverUrl'
// ...
}

Pour retirer une dépendance, « prouvez » au linter qu’elle n’a pas besoin d’être une dépendance. Par exemple, vous pouvez déplacer serverUrl hors de votre composant pour lui prouver qu’elle n’est pas réactive et ne changera pas d’un rendu à l’autre :

const serverUrl = 'https://localhost:1234'; // Ce n’est plus une valeur réactive

function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ Toutes les dépendances sont déclarées
// ...
}

À présent que serverUrl n’est plus une valeur réactive (et ne peut plus changer d’un rendu à l’autre), elle n’a plus besoin d’être déclarée comme dépendance. Si le code de votre Effet n’utilise aucune valeur réactive, sa liste de dépendances devrait être vide ([]) :

const serverUrl = 'https://localhost:1234'; // Ce n’est plus une valeur réactive
const roomId = 'music'; // Ce n’est plus une valeur réactive

function ChatRoom() {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, []); // ✅ Toutes les dépendances sont déclarées
// ...
}

Un Effet avec des dépendances vides n’est pas re-exécuté lorsque les valeurs des props ou de l’état de votre composant changent.

Piège

Si vous avez une base de code existante, vous trouverez peut-être des Effets qui réduisent le linter au silence comme ceci :

useEffect(() => {
// ...
// 🔴 Évitez de réduire ainsi le *linter* au silence :
// eslint-ignore-next-line react-hooks/exhaustive-deps
}, []);

Lorsque les dépendances ne correspondent pas au code, il y a un risque élevé de bugs. En réduisant le linter au silence, vous « mentez » à React quant aux valeurs dont dépend votre Effet. Au lieu de ça, prouvez qu”elles sont superflues.

Exemples de définitions de dépendances réactives

Exemple 1 sur 3 ·
Passer un tableau de dépendances

Si vous spécifiez des dépendances, votre Effet est exécuté après le rendu initial et après les nouveaux rendus qui modifient ces dépendances.

useEffect(() => {
// ...
}, [a, b]); // Re-exécuté si a ou b ont changé

Dans l’exemple ci-dessous, serverUrl et roomId sont des valeurs réactives, qui doivent donc toutes les deux être listées comme dépendances. Du coup, sélectionner un autre salon dans la liste déroulante ou modifier l’URL du serveur dans le champ de saisie entraînera une reconnexion de la discussion. En revanche, puisque message n’est pas utilisé par l’Effet (et n’est donc pas une dépendance), modifier le message n’entraîne pas de reconnexion.

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');
  const [message, setMessage] = useState('');

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, [serverUrl, roomId]);

  return (
    <>
      <label>
        URL du serveur :{' '}
        <input
          value={serverUrl}
          onChange={e => setServerUrl(e.target.value)}
        />
      </label>
      <h1>Bienvenue dans le salon {roomId} !</h1>
      <label>
        Votre message :{' '}
        <input value={message} onChange={e => setMessage(e.target.value)} />
      </label>
    </>
  );
}

export default function App() {
  const [show, setShow] = useState(false);
  const [roomId, setRoomId] = useState('general');
  return (
    <>
      <label>
        Choisissez le salon de discussion :{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">Général</option>
          <option value="travel">Voyage</option>
          <option value="music">Musique</option>
        </select>
        <button onClick={() => setShow(!show)}>
          {show ? 'Fermer le salon' : 'Ouvrir le salon'}
        </button>
      </label>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId}/>}
    </>
  );
}


Mettre à jour l’état sur base d’un état précédent, au sein d’un Effet

Lorsque vous souhaitez mettre à jour l’état sur base d’un état précédent depuis un Effet, vous risquez de rencontrer un problème :

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

useEffect(() => {
const intervalId = setInterval(() => {
setCount(count + 1); // Vous souhaitez incrémenter le compteur à chaque seconde...
}, 1000)
return () => clearInterval(intervalId);
}, [count]); // 🚩 ... mais préciser `count` comme dépendance réinitialise l'intervalle à chaque fois.
// ...
}

Dans la mesure où count est une valeur réactive, elle doit figurer dans la liste des dépendances. Pourtant, ça force l’Effet à se nettoyer et se remettre en place chaque fois que count change. C’est loin d’être idéal.

Pour corriger ça, passez une fonction de mise à jour d’état c => c + 1 à setCount :

import { useState, useEffect } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setCount(c => c + 1); // ✅ Passe une fonction de mise à jour
    }, 1000);
    return () => clearInterval(intervalId);
  }, []); // ✅ count n’est plus une dépendance

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

Maintenant que vous passez c => c + 1 au lieu de count + 1, votre Effet n’a plus besoin de dépendre de count. En conséquence, il n’aura plus besoin de nettoyer et remettre en place l’intervalle chaque fois que count change.


Supprimer des dépendances objets superflues

Si votre Effet dépend d’un objet ou d’une fonction créée lors du rendu, il s’exécutera sans doute trop souvent. Par exemple, cet Effet se reconnecte à chaque rendu parce que l’objet options est en réalité un objet différent à chaque rendu :

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

const options = { // 🚩 Cet objet est (re)créé à chaque rendu
serverUrl: serverUrl,
roomId: roomId
};

useEffect(() => {
const connection = createConnection(options); // L’Effet l’utilise
connection.connect();
return () => connection.disconnect();
}, [options]); // 🚩 Les dépendances sont donc différentes à chaque rendu
// ...

Évitez d’utiliser un objet créé lors du rendu comme dépendance. Préférez créer cet objet au sein de l’Effet :

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    const options = {
      serverUrl: serverUrl,
      roomId: roomId
    };
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return (
    <>
      <h1>Bienvenue dans le salon {roomId} !</h1>
      <input value={message} onChange={e => setMessage(e.target.value)} />
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  return (
    <>
      <label>
        Choisissez le salon de discussion :{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">Général</option>
          <option value="travel">Voyage</option>
          <option value="music">Musique</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

Maintenant que vous créez l’objet options au sein de l’Effet, l’Effet lui-même ne dépend plus que de la chaîne de caractères roomId.

Grâce à ce correctif, modifier la saisie ne reconnecte pas la discussion. Contrairement à un objet créé de frais à chaque fois, un texte comme roomId ne change pas tant qu’on n’en modifie pas la valeur. Apprenez-en davantage sur l’allègement des dépendances.


Supprimer des dépendances fonctions superflues

Si votre Effet dépend d’un objet ou d’une fonction créée lors du rendu, il s’exécutera sans doute trop souvent. Par exemple, cet Effet se reconnecte à chaque rendu parce que la fonction createOptions est une fonction différente à chaque rendu :

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

function createOptions() { // 🚩 Cette fonction est (re)créée à chaque rendu
return {
serverUrl: serverUrl,
roomId: roomId
};
}

useEffect(() => {
const options = createOptions(); // L’Effet l’utilise
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // 🚩 Les dépendances sont donc différentes à chaque rendu
// ...

En soi, créer une fonction à chaque rendu n’est pas un problème. Vous n’avez pas besoin d’optimiser ça. Mais si vous l’utilisez comme dépendance d’un Effet, elle forcera votre Effet à être ré-exécuté à chaque rendu.

Évitez d’utiliser une fonction créée lors du rendu comme dépendance. Déclarez-la plutôt au sein de l’Effet :

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    function createOptions() {
      return {
        serverUrl: serverUrl,
        roomId: roomId
      };
    }

    const options = createOptions();
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return (
    <>
      <h1>Bienvenue dans le salon {roomId} !</h1>
      <input value={message} onChange={e => setMessage(e.target.value)} />
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  return (
    <>
      <label>
        Choisissez le salon de discussion :{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">Général</option>
          <option value="travel">Voyage</option>
          <option value="music">Musique</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

Maintenant que vous déclarez la fonction createOptions au sein de l’Effet, l’Effet lui-même ne dépend plus que de la chaîne de caractères roomId.

Grâce à ce correctif, modifier la saisie ne reconnecte pas la discussion. Contrairement à une fonction créée de frais à chaque fois, un texte comme roomId ne change pas tant qu’on n’en modifie pas la valeur. Apprenez-en davantage sur l’allègement des dépendances.


Lire les dernières props et états à jour depuis un Effet

En construction

Cette section décrit une API expérimentale : elle n’a donc pas encore été livrée dans une version stable de React.

Par défaut, lorsque vous lisez une valeur réactive depuis un Effet, vous devez l’ajouter comme dépendance. Ça garantit que votre Effet « réagit » à chaque modification de cette valeur. Pour la plupart des dépendances, c’est bien le comportement que vous souhaitez.

Toutefois, il peut arriver que vous souhaitiez lire les dernières valeurs à jour de props ou d’états depuis un Effet, sans pour autant y « réagir ». Imaginons par exemple que vous souhaitiez afficher en console le nombre d’éléments dans le panier d’achats à chaque visite de la page :

function Page({ url, shoppingCart }) {
useEffect(() => {
logVisit(url, shoppingCart.length);
}, [url, shoppingCart]); // ✅ Toutes les dépendances sont déclarées
// ...
}

Et si vous vouliez afficher une visite de page après chaque modification de url, mais pas lorsque seul shoppingCart change ? Vous ne pouvez pas exclure shoppingCart de vos dépendances sans enfreindre les règles de la réactivité. En revanche, vous pouvez exprimer que vous ne souhaitez pas qu’un bout de votre code « réagisse » aux changements, même s’il est appelé depuis un Effet. Déclarez un Événement d’Effet avec le Hook useEffectEvent, et déplacez le code qui consulte shoppingCart à l’intérieur :

function Page({ url, shoppingCart }) {
const onVisit = useEffectEvent(visitedUrl => {
logVisit(visitedUrl, shoppingCart.length)
});

useEffect(() => {
onVisit(url);
}, [url]); // ✅ Toutes les dépendances sont déclarées
// ...
}

Les Événements d’Effets ne sont pas réactifs et doivent toujours être omis des dépendances de votre Effet. C’est ce qui vous permet d’y mettre du code non réactif (qui peut donc lire la dernière valeur en date de props ou d’états). En lisant shoppingCart au sein de onVisit, vous garantissez que shoppingCart ne redéclenchera pas votre Effet.

Découvrez en quoi les Événements d’Effets vous permettent de séparer les codes réactif et non réactif.


Afficher un contenu différent côté serveur et côté client

Si votre appli utilise du rendu côté serveur (que ce soit en direct ou via un framework), votre composant fera son rendu dans deux environnements différents. Côté serveur, son rendu produira le HTML initial. Côté client, React exécutera à nouveau le code de rendu pour pouvoir inscrire les gestionnaires d’événements à ce HTML. C’est pourquoi, afin que l’hydratation puisse fonctionner, votre résultat de rendu initial doit être identique côté client et côté serveur.

Dans de rares cas, vous pourriez avoir besoin de produire des contenus distincts côté client. Disons par exemple que votre appli lit certaines données depuis localStorage, il ne peut clairement pas faire ça côté serveur. Voici comment vous implémenteriez ça :

function MyComponent() {
const [didMount, setDidMount] = useState(false);

useEffect(() => {
setDidMount(true);
}, []);

if (didMount) {
// ... renvoi du JSX pour le client seulement ...
} else {
// ... renvoi du JSX initial ...
}
}

Pendant que l’appli charge, l’utilisateur voit le résultat du rendu initial. Puis, lorsqu’elle sera chargée et hydratée, votre Effet sera exécuté et définira didMount à true, ce qui déclenchera un nouveau rendu. On basculera alors sur le résultat de rendu pour le client seulement. Les Effets ne sont pas exécutés côté serveur, c’est pourquoi didMount resterait à false lors du rendu initial.

N’abusez pas de cette astuce. Gardez à l’esprit que les utilisateurs avec des connexions lentes verront le contenu initial pendant un bon bout de temps — jusqu’à plusieurs secondes — et qu’il faudrait donc éviter d’appliquer au final des changements trop drastiques dans l’apparence de votre composant. Le plus souvent, vous pourrez éviter de recourir à cette approche en utilisant des affichages conditionnels via CSS.


Dépannage

Mon Effet est exécuté deux fois au montage du composant

Lorsque le Mode Strict est activé, en développement, React exécutera une première fois la mise en place et le nettoyage, avant la mise en place effective.

C’est une mise à l’épreuve pour vérifier que la logique de votre Effet est implémentée correctement. Si ça entraîne des problèmes, c’est que votre code de nettoyage est manquant ou incomplet. La fonction de nettoyage devrait arrêter ou défaire ce que la fonction de mise en place a initié. La règle à suivre est simple : l’utilisateur ne devrait pas pouvoir faire la différence entre une exécution unique de la mise en place (comme en production) et une séquence mise en placenettoyagemise en place (comme en développement).

Découvrez en quoi ça vous aide à repérer des bugs et comment corriger votre code.


Mon Effet est exécuté après chaque rendu

Commencez par vérifier que vous n’avez pas oublié de spécifier le tableau des dépendances :

useEffect(() => {
// ...
}); // 🚩 Aucun tableau de dépendance : exécuté après chaque rendu !

Si vous avez spécifié un tableau de dépendances et que votre Effet persiste à s’exécuter en boucle, c’est parce qu’une de vos dépendances est différente à chaque rendu.

Vous pouvez déboguer ce problème en affichant vos dépendances en console :

useEffect(() => {
// ..
}, [serverUrl, roomId]);

console.log([serverUrl, roomId]);

Vous pouvez alors cliquer bouton droit, dans la console, sur les tableaux issus de différents rendus et sélectionner « Stocker objet en tant que variable globale » pour chacun d’entre eux. En supposant que vous avez stocké le premier en tant que temp1 et le second en tant que temp2, vous pouvez alors utiliser la console du navigateur pour vérifier si chaque dépendance des tableaux est identique :

Object.is(temp1[0], temp2[0]); // La première dépendance est-elle inchangée ?
Object.is(temp1[1], temp2[1]); // La deuxième dépendance est-elle inchangée ?
Object.is(temp1[2], temp2[2]); // ... et ainsi de suite pour chaque dépendance ...

Lorsque vous aurez repéré la dépendance qui diffère d’un rendu à l’autre, vous pouvez généralement corriger ça de l’une des manières suivantes :

En tout dernier recours (si aucune de ces approches n’a résolu le souci), enrobez la création de la dépendance avec useMemo (ou useCallback pour les fonctions).


Mon Effet n’arrête pas de se re-exécuter

Si votre Effet s’exécute en boucle infinie, deux choses devraient se passer :

  • Votre Effet met à jour un état.
  • Cet état entraîne un nouveau rendu, qui modifie les dépendances de votre Effet.

Avant de vous attaquer à la résolution de ce problème, demandez-vous si votre Effet se connecte à un système extérieur (tel que le DOM, le réseau, un widget tiers, etc.). Pourquoi votre Effet a-t-il besoin de modifier l’état ? Se synchronise-t-il avec un système extérieur ? Ou essayez-vous de gérer le flux de données de votre application avec ça ?

S’il n’y a pas de système extérieur, envisagez de retirer carrément l’Effet pour simplifier votre logique.

Si vous vous synchronisez effectivement avec un système extérieur, réfléchissez aux conditions dans lesquelles votre Effet devrait mettre à jour l’état. Quelque chose a-t-il changé qui impacte le résultat visuel de votre composant ? Si vous devez surveiller certaines données inutilisées par le rendu, une ref (qui ne redéclenche pas de rendu) serait peut-être plus appropriée. Vérifiez que votre Effet ne met pas à jour l’état (entraînant un nouveau rendu) plus que nécessaire.

Pour finir, si votre Effet met à jour l’état au bon moment, mais qu’il y a tout de même une boucle, c’est sans doute parce que la mise à jour de l’état entraîne la modification d’une des dépendances de l’Effet. Voyez comment déboguer les modifications de dépendances.


Ma logique de nettoyage est exécutée alors que mon composant n’a pas été démonté

La fonction de nettoyage n’est pas exécutée seulement lors du démontage, mais avant chaque nouveau rendu dont les dépendances ont changé. Qui plus est, en développement, React exécute la mise en place et le nettoyage une fois de plus juste après le montage du composant.

Si vous avez du code de nettoyage qui ne correspond à aucun code de mise en place, c’est généralement mauvaise signe :

useEffect(() => {
// 🔴 À éviter : code de nettoyage sans mise en place correspondante
return () => {
doSomething();
};
}, []);

Votre code de nettoyage devrait refléter celui de mise en place, qu’il devrait arrêter ou défaire :

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);

Apprenez en quoi le cycle de vie des Effets diffère de celui des composants.


Mon Effet fait un truc visuel, et l’affichage vacille avant son exécution

Si votre Effet doit empêcher le navigateur de rafraîchir immédiatement l’affichage à l’écran, remplacez useEffect par useLayoutEffect. Remarquez que ça ne devrait concerner qu’une toute petite minorité de cas. Vous n’aurez besoin de ça que lorsqu’il est crucial que votre Effet soit exécuté avant le rafraîchissement par le navigateur ; par exemple, pour mesurer et positionner une infobulle avant que l’utilisateur ne la voie.