Pitfall

useLayoutEffect pode prejudicar o desempenho. Use useEffect sempre que possível.

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);
}, []);
// ...

Veja mais exemplos abaixo.

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 de setup. 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 o Object.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 o useEffect.


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:

  1. Renderize a ferramenta de dica em qualquer lugar (mesmo com uma posição incorreta).
  2. Meça sua altura e decida onde colocar a ferramenta de dica.
  3. 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:

  1. O Tooltip renderiza com o valor inicial do state (estado) tooltipHeight = 0 (portanto, a ferramenta de dica pode estar posicionada incorretamente).
  2. O React o adiciona no DOM e executa o código do useLayoutEffect.
  3. Seu useLayoutEffect mede a altura do conteúdo da ferramenta de dica e altera o valor do state, desencadeando uma nova renderização imediatamente.
  4. O Tooltip renderiza novamente com o state (estado) tooltipHeight contendo o valor correto da altura (então a ferramenta de dica é posicionada corretamente).
  5. 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.

useLayoutEffect vs useEffect

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
  );
}

Note

Renderizar em duas etapas bloqueando o navegador prejudica o desempenho. Tente evitar isso sempre que for possível.


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:

  1. Renderize o conteúdo inicial.
  2. Meça o layout antes do navegador exibir a tela.
  3. 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 pelo useEffect. 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) booleano isMounted que é iniciado como false, e defina-o como true dentro de uma chamada de useEffect. 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 o useLayoutEffect. Então, o React substituirá isso por RealContent que é executado apenas no lado do cliente e pode incluir chamadas de useLayoutEffect.

  • 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 usar useSyncExternalStore que oferece suporte a renderização no lado servidor.