PiĂšge

useLayoutEffect peut nuire aux performances. Préférez autant que possible useEffect.

useLayoutEffect est une version de useEffect qui est dĂ©clenchĂ©e avant que le navigateur ne rafraĂźchisse l’affichage.

useLayoutEffect(setup, dependencies?)

Référence

useLayoutEffect(setup, dependencies?)

Appelez useLayoutEffect pour effectuer des mesures de mise en page avant que la navigateur ne rafraĂźchisse l’affichage Ă  l’écran :

import { useState, useRef, useLayoutEffect } from 'react';

function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0);

useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height);
}, []);
// ...

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.

Valeur renvoyée

useLayoutEffect renvoie undefined.

Limitations

  • useLayoutEffect 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.

  • 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.

  • Les Effets ne sont exĂ©cutĂ©s que cĂŽtĂ© client. Ils sont ignorĂ©s lors du rendu cĂŽtĂ© serveur.

  • Le code dans useLayoutEffect et toutes les mises Ă  jour d’état qui y sont demandĂ©es empĂȘchent le navigateur de rafraĂźchir l’affichage Ă  l’écran. Si vous l’utilisez trop, ça ralentira votre appli. Autant que possible, prĂ©fĂ©rez useEffect.

  • Si vous dĂ©clenchez une mise Ă  jour d’état au sein d’un useLayoutEffect, React exĂ©cutera immĂ©diatement tous les Effets restants, y compris les useEffect.


Utilisation

Mesurer la mise en page avant que le navigateur ne rafraĂźchisse l’écran

La plupart des composants n’ont pas besoin de connaĂźtre leur position ou leurs dimensions Ă  l’écran pour dĂ©terminer ce qu’ils affichent. Ils renvoient simplement du JSX, aprĂšs quoi le navigateur calcule leur mise en page (position et taille) et rafraĂźchit l’écran.

Parfois cependant, ça ne suffit pas. Imaginez une infobulle qui doit apparaĂźtre Ă  cĂŽtĂ© d’un Ă©lĂ©ment quand on survole ce dernier. S’il y a suffisamment de place, l’infobulle devrait apparaĂźtre au-dessus de l’élĂ©ment, mais si c’est trop Ă©troit, elle devrait apparaĂźtre en dessous. Pour afficher l’infobulle dans la bonne position d’entrĂ©e de jeu, vous aurez besoin de connaĂźtre sa hauteur (afin de dĂ©terminer si elle tiendra au-dessus).

Pour y parvenir, vous devrez faire un rendu en deux temps :

  1. Faire le rendu de l’infobulle n’importe oĂč (mĂȘme dans la mauvaise position).
  2. Mesurer sa hauteur et dĂ©cider oĂč la placer.
  3. Faire à nouveau le rendu de l’infobulle au bon endroit.

Tout ça doit se passer avant que le navigateur ait rafraĂźchi l’affichage. Vous ne voulez surtout pas que l’utilisateur voie l’infobulle se dĂ©placer. Appelez useLayoutEffect pour mesurer la mise en page avant le rafraĂźchissement de l’écran :

function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0); // Vous ne connaissez pas encore sa hauteur

useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height); // Refaire un rendu maintenant que vous connaissez la véritable hauteur
}, []);

// ...utilisez tooltipHeight dans la logique de rendu qui suit...
}

Voici comment ça fonctionne, Ă©tape par Ă©tape :

  1. Tooltip fait un premier rendu avec tooltipHeight = 0 (du coup l’infobulle est peut-ĂȘtre mal positionnĂ©e).
  2. React met tout ça dans le DOM et exécute le code dans useLayoutEffect.
  3. Votre useLayoutEffect mesure la hauteur du contenu de l’infobulle et dĂ©clenche immĂ©diatement un nouveau rendu.
  4. Tooltip refait son rendu avec la vĂ©ritable hauteur dans tooltipHeight (du coup l’infobulle est correctement positionnĂ©e).
  5. React met à jour le DOM, et le navigateur peut enfin afficher l’infobulle.

Survolez les boutons ci-dessous pour voir de quelle façon l’infobulle ajuste sa position en fonction de la place dont elle dispose :

import { useRef, useLayoutEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import TooltipContainer from './TooltipContainer.js';

export default function Tooltip({ children, targetRect }) {
  const ref = useRef(null);
  const [tooltipHeight, setTooltipHeight] = useState(0);

  useLayoutEffect(() => {
    const { height } = ref.current.getBoundingClientRect();
    setTooltipHeight(height);
    console.log('Hauteur mesurĂ©e de l’infobulle : ' + height);
  }, []);

  let tooltipX = 0;
  let tooltipY = 0;
  if (targetRect !== null) {
    tooltipX = targetRect.left;
    tooltipY = targetRect.top - tooltipHeight;
    if (tooltipY < 0) {
      // Ça ne tient pas au-dessus, donc on la place en dessous.
      tooltipY = targetRect.bottom;
    }
  }

  return createPortal(
    <TooltipContainer x={tooltipX} y={tooltipY} contentRef={ref}>
      {children}
    </TooltipContainer>,
    document.body
  );
}

Remarquez que mĂȘme si le composant Tooltip a besoin de faire un rendu en deux temps (une premiĂšre fois avec tooltipHeight initialisĂ©e Ă  0, puis avec la vĂ©ritable hauteur mesurĂ©e), vous ne voyez que le rĂ©sultat final. C’est pourquoi vous devez utiliser useLayoutEffect plutĂŽt que useEffect dans cet exemple. Examinons les diffĂ©rences en dĂ©tails ci-dessous.

useLayoutEffect vs. useEffect

Exemple 1 sur 2 Â·
useLayoutEffect empĂȘche le navigateur de rafraĂźchir l’affichage

React garantit que le code au sein de useLayoutEffect et toutes les mises Ă  jour d’état qui y sont demandĂ©es seront traitĂ©s avant que le navigateur ne rafraĂźchisse l’affichage Ă  l’écran. Ça vous permet de faire un rendu de l’infobulle, la mesurer, et refaire un rendu de l’infobulle sans que l’utilisateur puisse remarquer le premier rendu supplĂ©mentaire. En d’autres termes, useLayoutEffect empĂȘche le navigateur de rafraĂźchir l’affichage.

import { useRef, useLayoutEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import TooltipContainer from './TooltipContainer.js';

export default function Tooltip({ children, targetRect }) {
  const ref = useRef(null);
  const [tooltipHeight, setTooltipHeight] = useState(0);

  useLayoutEffect(() => {
    const { height } = ref.current.getBoundingClientRect();
    setTooltipHeight(height);
  }, []);

  let tooltipX = 0;
  let tooltipY = 0;
  if (targetRect !== null) {
    tooltipX = targetRect.left;
    tooltipY = targetRect.top - tooltipHeight;
    if (tooltipY < 0) {
      // Elle ne tient pas au-dessus, donc on la place en dessous.
      tooltipY = targetRect.bottom;
    }
  }

  return createPortal(
    <TooltipContainer x={tooltipX} y={tooltipY} contentRef={ref}>
      {children}
    </TooltipContainer>,
    document.body
  );
}

Remarque

Faire un rendu en deux temps et bloquer le navigateur nuit aux performances. Essayez d’éviter ça autant que possible.


Dépannage

J’ai une erreur : “useLayoutEffect does nothing on the server”

(« useLayoutEffect ne fait rien cĂŽtĂ© serveur Â», NdT)

L’objectif de useLayoutEffect consiste Ă  permettre Ă  votre composant d’utiliser des infos de mise en page pour son rendu :

  1. Faire le rendu initial
  2. Mesurer la mise en page avant que le navigateur ne rafraĂźchisse l’écran.
  3. Faire le rendu final en utilisant les infos de mise en page obtenues.

Lorsque vous ou votre framework utilisez le rendu cĂŽtĂ© serveur, votre appli React produit du HTML cĂŽtĂ© serveur pour le rendu initial. Ça permet d’afficher ce HTML initial avant que le code JavaScript ne soit chargĂ©.

Le problĂšme est que cĂŽtĂ© serveur, il n’y a aucune information de mise en page.

Dans l’exemple prĂ©cĂ©dent, l’appel Ă  useLayoutEffect dans le composant Tooltip lui permet de se positionner correctement (au-dessus ou en dessous du contenu), en fonction de la hauteur de son contenu. Si vous tentiez d’afficher Tooltip au sein du HTML initial produit par le serveur, il serait impossible d’en dĂ©terminer la hauteur. CĂŽtĂ© serveur, il n’y a pas encore de mise en page ! Du coup, mĂȘme si vous en faites le rendu cĂŽtĂ© serveur, sa position « sursautera Â» cĂŽtĂ© client aprĂšs que le JavaScript aura Ă©tĂ© chargĂ© et exĂ©cutĂ©.

En gĂ©nĂ©ral, les composants qui reposent sur des infos de mise en page n’ont de toutes façons pas besoin d’ĂȘtre rendus cĂŽtĂ© serveur. Par exemple, ça n’a probablement pas de sens d’afficher un Tooltip lors du rendu initial. Il est dĂ©clenchĂ© par une interaction utilisateur.

Quoi qu’il en soit, si vous rencontrez ce problĂšme, vous avez quelques options :

  • Remplacez useLayoutEffect par useEffect. Ça dit Ă  React qu’il peut afficher le rendu initial sans bloquer le rafraĂźchissement (puisque le HTML d’origine deviendra visible avant que votre Effet ne soit exĂ©cutĂ©).

  • Vous pouvez aussi indiquer que votre composant est rĂ©servĂ© au cĂŽtĂ© client. Ça indique Ă  React qu’il faudra en remplacer le contenu jusqu’au pĂ©rimĂštre <Suspense> le plus proche par un contenu de secours (par exemple un spinner ou un squelette structurel) pendant le rendu cĂŽtĂ© serveur.

  • Vous pouvez encore ne faire le rendu d’un composant qui recourt Ă  useLayoutEffect qu’aprĂšs l’hydratation. Maintenez un Ă©tat boolĂ©en isMounted initialisĂ© Ă  false, que vous mettrez Ă  true au sein d’un appel Ă  useEffect. Votre logique de rendu peut alors ressembler Ă  return isMounted ? <RealContent /> : <FallbackContent />. CĂŽtĂ© serveur et pendant l’hydratation, l’utilisateur verra le FallbackContent qui, lui, n’appellera pas useLayoutEffect. Puis React le remplacera par RealContent qui s’exĂ©cute cĂŽtĂ© client uniquement et pourra inclure des appels Ă  useLayoutEffect.

  • Si vous synchronisez votre composant avec un stockage de donnĂ©es extĂ©rieur et vous appuyez sur useLayoutEffect pour des raisons autres que la mesure de la mise en page, envisagez d’utiliser plutĂŽt useSyncExternalStore qui, lui, prend en charge le rendu cĂŽtĂ© serveur.