useLayoutEffect
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 desetup
. 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 comparaisonObject.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Ă©rezuseEffect
. -
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 lesuseEffect
.
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 :
- Faire le rendu de lâinfobulle nâimporte oĂč (mĂȘme dans la mauvaise position).
- Mesurer sa hauteur et dĂ©cider oĂč la placer.
- 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 :
Tooltip
fait un premier rendu avectooltipHeight = 0
(du coup lâinfobulle est peut-ĂȘtre mal positionnĂ©e).- React met tout ça dans le DOM et exĂ©cute le code dans
useLayoutEffect
. - Votre
useLayoutEffect
mesure la hauteur du contenu de lâinfobulle et dĂ©clenche immĂ©diatement un nouveau rendu. Tooltip
refait son rendu avec la véritable hauteur danstooltipHeight
(du coup lâinfobulle est correctement positionnĂ©e).- 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.
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 ); }
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 :
- Faire le rendu initial
- Mesurer la mise en page avant que le navigateur ne rafraĂźchisse lâĂ©cran.
- 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
paruseEffect
. Ă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Ă©enisMounted
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 leFallbackContent
qui, lui, nâappellera pasuseLayoutEffect
. Puis React le remplacera parRealContent
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ĂŽtuseSyncExternalStore
qui, lui, prend en charge le rendu cÎté serveur.