useLayoutEffect
useLayoutEffect
é uma versão de useEffect
que é executada antes do navegador exibir a tela.
useLayoutEffect(setup, dependencies?)
Referência
useLayoutEffect(setup, dependencies?)
Chame o useLayoutEffect
para executar as medidas de layout antes do navegador exibir a tela:
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);
}, []);
// ...
Parâmetros
-
setup
: A função com a lógica do seu Effect (efeito). Sua função de configuração também pode opcionalmente retornar uma função de limpeza (cleanup). Antes que o seu componente seja adicionado ao DOM, o React executará a sua função de configuração. Após cada re-renderização por meio das dependências alteradas, o React primeiro executará a função de limpeza (se fornecida) com os valores antigos e, em seguida, executará a sua função de configuração com os novos valores. Antes que o seu componente seja removido do DOM, o React executará a sua função de limpeza. -
opcional
dependencies
: A lista de todos os valores reativos referenciados dentro do código desetup
. Valores reativos incluem props, states e todas as variáveis e funções declaradas diretamente no body do seu componente. Se o seu linter estiver configurado para o React, ele verificará se cada valor reativo está corretamente especificado como uma dependência. A lista de dependências deve ter um número constante de itens e ser escrita inline, como por exemplo:[dep1, dep2, dep3]
. O React fará uma comparação de cada dependência com seu valor anterior usando oObject.is
. Se você omitir esse argumento, seu Effect (efeito) será executado novamente após cada nova re-renderização do componente.
Retorno
useLayoutEffect
retorna undefined
.
Ressalvas
-
useLayoutEffect
é um Hook, então você só pode chamá-lo no nível superior do seu componente ou nos seus próprios Hooks. Não é possível chamá-lo dentro de loops ou condições. Se você precisar fazer isso, crie um componente e mova seu Effect (efeito) para lá. -
Quando o Strict Mode (Modo Estrito) está ativado, o React executará um ciclo extra de setup+cleanup (configuração+limpeza) exclusivamente para modo de desenvolvimento antes do primeiro ciclo de configuração real. Isso é um teste de estresse que garante que sua lógica de cleanup (limpeza) “espelhe” sua lógica de setup (configuração) e que ela interrompa ou desfaça qualquer coisa que o setup (configuração) esteja fazendo. Se isso lhe causar um problema, implemente a função de cleanup (limpeza).
-
Se algumas de suas dependências são objetos ou funções definidas dentro do componente, há o risco de que elas façam o Effect (efeito) ser executado mais vezes do que o necessário. Para corrigir isso, remova as dependências com objetos e funções desnecessárias. Você também pode extrair as atualizações de state (estado) e sua lógica não reativa para fora do seu Effect (efeito).
-
Os Effects (efeitos) só são executados no lado do cliente. Eles não são executados durante a renderização no lado do servidor.
-
O código executado dentro do
useLayoutEffect
e todas as atualizações de state (estado) agendadas a partir dele bloqueiam o navegador de exibir a tela. Quando usado em excesso, acaba tornando sua aplicação lenta. Sempre que possível, prefira usar ouseEffect
.
Uso
Medindo o layout antes do navegador exibir a tela
A maioria dos componentes não precisa saber sua posição e tamanho na tela para decidir o que renderizar. Eles apenas retornam algum JSX. Em seguida, o navegador calcula o layout deles (posição e tamanho) e exibe a tela.
Às vezes, somente isso não é suficiente. Imagine uma ferramenta de dica que aparece ao lado de algum elemento quando o mouse está sobre ele. Se houver espaço suficiente, a ferramenta de dica deve aparecer acima do elemento, mas se não couber, ela deve aparecer abaixo. Para renderizar a ferramenta de dica na posição final correta, você precisa saber a altura dela (ou seja, se ela se encaixa na parte superior).
Para fazer isso, é necessário renderizar duas vezes:
- Renderize a ferramenta de dica em qualquer lugar (mesmo com uma posição incorreta).
- Meça sua altura e decida onde colocar a ferramenta de dica.
- Renderize a ferramenta de dica novamente no local correto.
Tudo isso precisa acontecer antes do navegador exibir a tela. Você não quer que o usuário veja a ferramenta de dica se movendo. Chame o useLayoutEffect
para realizar as medições de layout antes do navegador exibir a tela:
function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0); // Você ainda não sabe qual a altura real
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height); // Re-renderize novamente agora já que você conhece a altura real
}, []);
// ...use o tooltipHeight na lógica de renderização em seguida...
}
Aqui está a explicação de como o código acima funciona passo a passo:
- O
Tooltip
renderiza com o valor inicial do state (estado)tooltipHeight = 0
(portanto, a ferramenta de dica pode estar posicionada incorretamente). - O React o adiciona no DOM e executa o código do
useLayoutEffect
. - Seu
useLayoutEffect
mede a altura do conteúdo da ferramenta de dica e altera o valor do state, desencadeando uma nova renderização imediatamente. - O
Tooltip
renderiza novamente com o state (estado)tooltipHeight
contendo o valor correto da altura (então a ferramenta de dica é posicionada corretamente). - O React a atualiza o DOM e finalmente o navegador exibe a ferramenta de dica.
Passe o mouse sobre os botões abaixo e observe como a ferramenta de dica ajusta a sua posição conforme a disponibilidade de espaço:
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('Medida da altura da ferramenta de dica: ' + height); }, []); let tooltipX = 0; let tooltipY = 0; if (targetRect !== null) { tooltipX = targetRect.left; tooltipY = targetRect.top - tooltipHeight; if (tooltipY < 0) { // Não cabe acima, então coloque abaixo. tooltipY = targetRect.bottom; } } return createPortal( <TooltipContainer x={tooltipX} y={tooltipY} contentRef={ref}> {children} </TooltipContainer>, document.body ); }
Observe que, mesmo o componente Tooltip
precisando ser renderizado em duas etapas (primeiro, com o tooltipHeight
inicializado com o valor 0
e em seguida, com a medida da altura real), você só visualiza o resultado final. Isso é o por que precisamos usar useLayoutEffect
ao invés de useEffect
para este cenário de exemplo. Vamos visualizar as diferenças com mais detalhes abaixo.
Example 1 of 2: useLayoutEffect
impede o navegador de exibir a tela
O React garante que o código dentro de useLayoutEffect
e quaisquer atualizações de state (estado) agendadas dentro dele serão processados antes do navegador exibir a tela. Isso permite que você renderize a ferramenta de dica, tire sua medida e renderize novamente sem que o usuário perceba a primeira renderização extra. Em outras palavras, o useLayoutEffect
bloqueia o navegador de realizar a construção da tela.
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) { // Não cabe acima, então coloque abaixo. tooltipY = targetRect.bottom; } } return createPortal( <TooltipContainer x={tooltipX} y={tooltipY} contentRef={ref}> {children} </TooltipContainer>, document.body ); }
Solução de Problemas
Estou recebendo o erro: “useLayoutEffect
does nothing on the server”
O propósito do useLayoutEffect
é permitir que seu componente use informações de layout para a renderização:
- Renderize o conteúdo inicial.
- Meça o layout antes do navegador exibir a tela.
- Renderize o conteúdo final usando as informações de layout que você recebeu.
Quando você ou seu framework utilizam a renderização no lado do servidor, sua aplicação React gera o HTML no servidor para a renderização inicial. Isso permite que você exiba o HTML inicial antes que o código JavaScript seja carregado.
O problema é que no lado do servidor não há informações de layout disponíveis.
No exemplo anterior, a chamada do useLayoutEffect
no componente Tooltip
permite que ele se posicione corretamente (acima ou abaixo do conteúdo) dependendo da altura do conteúdo. Se você tentasse renderizar o Tooltip
como parte do HTML inicial do servidor, isso seria impossível de determinar. No servidor, ainda não há layout! Portanto, mesmo que você o renderizasse no lado do servidor, sua posição “pularia” no lado do cliente após o carregamento e execução do JavaScript.
Normalmente, componentes que dependem de informações de layout não precisam ser renderizados no lado do servidor. Por exemplo, é provável que não se faça muito sentido mostrar um Tooltip
durante a renderização inicial, pois ele é acionado por meio de uma interação do usuário no lado do cliente.
No entanto, se encontrar esse problema, você possui essas opções:
-
Substituir o
useLayoutEffect
pelouseEffect
. Isso informa ao React que é aceitável exibir o resultado da renderização inicial sem bloquear a construção (porque o HTML original ficará visível antes de seu Effect (efeito) ser executado). -
Marque seu componente como exclusivo para o cliente. Isso diz ao React para substituir seu conteúdo até o limite mais próximo de
<Suspense>
por uma carga de fallback (por exemplo, um spinner ou um glimmer) durante a renderização no lado do servidor. -
Você também pode renderizar um componente com
useLayoutEffect
somente após a hidratação. Mantenha um state (estado) booleanoisMounted
que é iniciado comofalse
, e defina-o comotrue
dentro de uma chamada deuseEffect
. Sua lógica de renderização pode ser algo semelhante a isso:return isMounted ? <RealContent /> : <FallbackContent />
. No lado servidor e durante a hidratação, o usuário veráFallbackContent
que não deve chamar ouseLayoutEffect
. Então, o React substituirá isso porRealContent
que é executado apenas no lado do cliente e pode incluir chamadas deuseLayoutEffect
. -
Se você sincronizar seu componente com uma loja de dados externa e depender de
useLayoutEffect
por razões diferentes da medição de layout, considere usaruseSyncExternalStore
que oferece suporte a renderização no lado servidor.