Menu

useLayoutEffect en React : mesurer avant l'affichage

useLayoutEffect fonctionne comme useEffect mais s'exécute juste après la mise à jour du DOM par React et avant que le navigateur affiche la page. Utilisez-le pour mesurer un élément et ajuster la mise en page sans scintillement visible, et gardez useEffect pour tout le reste.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

useLayoutEffect est une version de useEffect qui s'exécute plus tôt : juste après que React a mis à jour le DOM, mais avant que le navigateur affiche la nouvelle image. Le code qu'il contient peut mesurer des éléments et modifier la mise en page, et l'utilisateur ne voit jamais que le résultat final. Il bloque l'affichage pendant son exécution, utilisez-le donc seulement quand vous avez besoin de ce moment précis.

Les effets sont déclarés dans l'ordre effet, puis effet de layout, mais la console affiche l'effet de layout en premier à chaque rendu. L'ordre de déclaration ne compte pas entre ces deux hooks ; leur moment d'exécution, si.

La syntaxe

La signature est identique à celle de useEffect : une fonction de mise en place, un nettoyage facultatif et un tableau de dépendances.

useLayoutEffect(() => {
    // runs after the DOM update, before paint
    return () => {
        // cleanup, before the next run and on unmount
    };
}, [dependencies]);

Tout ce que vous savez des dépendances et du nettoyage grâce à useEffect s'applique. La seule différence est le moment où React l'appelle.

Quand chacun s'exécute

Après un rendu, React met à jour le DOM puis fait trois choses dans un ordre fixe :

  1. Il exécute tous les nettoyages des effets de layout et les effets de layout, de façon synchrone.
  2. Il laisse le navigateur afficher la page. Si un effet de layout a modifié l'état, React refait d'abord un rendu, toujours avant l'affichage.
  3. Il exécute les effets ordinaires, généralement après l'affichage.

Dans un arbre, chaque effet de layout s'exécute avant tout effet ordinaire, et les enfants passent avant leurs parents, puisqu'un parent ne peut être terminé qu'une fois ses enfants en place :

La console affiche d'abord les deux effets de layout, Child puis Parent, puis les deux effets ordinaires dans le même ordre. Le key={n} fait que chaque clic monte un Parent neuf, vous obtenez donc les quatre mêmes lignes à chaque fois. Ajoutez à chaque effet un nettoyage qui écrit dans la console et regardez les anciens s'exécuter avant les nouveaux.

Le problème du scintillement

Imaginez une infobulle qui doit apparaître au-dessus de son bouton, sauf s'il n'y a pas de place au-dessus, auquel cas elle va en dessous. Vous ne pouvez savoir s'il y a de la place qu'une fois l'infobulle dans le DOM, quand vous pouvez mesurer sa hauteur. Le rendu se fait donc en deux passes :

  1. Afficher l'infobulle quelque part, pour pouvoir la mesurer.
  2. La mesurer, calculer la vraie position, modifier l'état, refaire un rendu.

Avec useEffect, le navigateur peut afficher la page entre ces deux passes, et l'utilisateur voit l'infobulle sauter de la première position à la seconde. Avec useLayoutEffect, la seconde passe a lieu avant l'affichage, la première position n'est donc jamais montrée.

Survolez le bouton du haut : il n'y a pas de place au-dessus, l'infobulle apparaît donc en dessous. Survolez celui du bas et elle apparaît au-dessus. La console vous indique quel côté la mesure a choisi. Remplacez useLayoutEffect par useEffect et survolez à nouveau : l'infobulle peut maintenant clignoter en haut de la boîte pendant une image avant de se déplacer. Sur une machine rapide, c'est souvent trop bref pour être remarqué, et c'est exactement pour cela que ce bug part en production puis est signalé depuis des téléphones plus lents.

offsetHeight, offsetTop et getBoundingClientRect() forcent tous le navigateur à calculer la mise en page, donc les lire dans l'effet de layout vous donne les vraies valeurs du DOM que React vient d'écrire.

Mesurer un élément

Le même pattern fonctionne pour toute taille dont dépend le rendu : lisez-la avec une ref dans un effet de layout, stockez-la dans l'état, faites le rendu avec.

Cliquez sur le bouton et la ligne de largeur se met à jour dans la même image que le texte. La page n'affiche jamais un nouveau texte à côté d'une ancienne largeur, car le second rendu a lieu avant l'affichage. Retirez [text] du tableau de dépendances et la mesure s'exécute après chaque rendu, y compris celui que provoque son propre setWidth ; elle ne s'arrête là que parce que la largeur ne change plus.

Préférez useEffect

Un effet de layout retarde l'affichage jusqu'à ce qu'il se termine, et s'il modifie l'état, jusqu'à la fin du rendu supplémentaire aussi. Un effet lent rend chaque mise à jour de la page lente. N'utilisez useLayoutEffect que si toutes ces conditions sont réunies :

  • L'effet lit la mise en page (taille, position, défilement).
  • Il modifie ce qui est affiché en fonction de cette lecture.
  • L'état intermédiaire serait visible et aurait l'air faux.

Tout le reste (récupération de données, abonnements, minuteurs, journalisation, analytics) a sa place dans useEffect. Une règle rapide : commencez par useEffect, et ne changez que si vous voyez un scintillement.

useEffect s'exécute généralement après l'affichage, mais pas toujours. Quand la mise à jour vient d'une action discrète de l'utilisateur comme un clic ou une frappe au clavier, React peut exécuter les effets avant que le navigateur affiche la page. C'est l'une des raisons pour lesquelles un scintillement peut apparaître dans une situation et pas dans une autre.

Rendu serveur

Les effets ne s'exécutent jamais pendant le rendu serveur, et useLayoutEffect non plus. Le serveur envoie le HTML du premier rendu, et l'effet de layout ne s'exécute qu'après l'hydratation dans le navigateur. React 18 et les versions antérieures affichaient l'avertissement "useLayoutEffect does nothing on the server" quand un tel composant était rendu sur le serveur ; React 19 a supprimé cet avertissement.

Si la première image rendue par le serveur avait l'air fausse sans la mesure, il existe deux solutions courantes : afficher un contenu neutre de repli (par exemple, masquer l'infobulle jusqu'à ce qu'elle soit mesurée), ou n'afficher le composant que côté client :

function ClientOnly({ children }) {
    const [mounted, setMounted] = useState(false);
    useEffect(() => setMounted(true), []);
    return mounted ? children : null;
}

useInsertionEffect

Il existe un troisième hook d'effet, useInsertionEffect, qui s'exécute avant tout effet de layout. Il existe pour les bibliothèques CSS-in-JS qui génèrent des balises <style> à l'exécution : y insérer les styles garantit qu'un effet de layout qui mesure un élément le voit déjà stylé. Il ne peut pas mettre à jour l'état et les refs ne sont pas encore attachées, il n'est donc d'aucune utilité dans le code d'une application. Si vous n'écrivez pas une bibliothèque de styles, vous n'en aurez pas besoin.

Questions fréquentes

Quelle est la différence entre useEffect et useLayoutEffect ?

Le moment d'exécution. useLayoutEffect s'exécute de façon synchrone après que React a modifié le DOM mais avant que le navigateur affiche la page, donc ses mises à jour d'état sont appliquées avant que l'utilisateur voie quoi que ce soit. useEffect s'exécute généralement après l'affichage. L'API et le tableau de dépendances sont identiques.

Quand utiliser useLayoutEffect ?

Quand un effet lit la mise en page (une taille ou une position) et modifie aussitôt ce qui est affiché en fonction de cela, par exemple pour placer une infobulle au-dessus ou en dessous de son bouton. Sans lui, l'utilisateur peut voir pendant une image la première position, incorrecte.

useLayoutEffect est-il mauvais pour les performances ?

Il empêche le navigateur d'afficher la page tant qu'il n'a pas fini, ainsi que tout nouveau rendu qu'il déclenche. Un effet de layout lent rend toute la page lente, utilisez donc useEffect sauf si vous devez éviter un scintillement visible.

useLayoutEffect s'exécute-t-il sur le serveur ?

Non. Aucun des deux effets ne s'exécute pendant le rendu serveur. Les anciennes versions de React affichaient un avertissement quand un composant avec useLayoutEffect était rendu sur le serveur ; React 19 ne le fait plus, mais l'effet ne s'exécute toujours que dans le navigateur.

Illustration des langages de programmation de Coddy

Apprendre à coder avec Coddy

COMMENCER