Menu

useLayoutEffect no React: medir antes de o navegador pintar

useLayoutEffect funciona como useEffect, mas executa logo depois que o React atualiza o DOM e antes de o navegador pintar a tela. Use-o para medir um elemento e ajustar o layout sem uma piscada visível, e deixe o useEffect para todo o resto.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

useLayoutEffect é uma versão do useEffect que executa mais cedo: logo depois que o React atualizou o DOM, mas antes de o navegador pintar o novo frame. O código dentro dele pode medir elementos e mudar o layout, e o usuário só vê o resultado final. Ele bloqueia a pintura enquanto executa, então use-o só quando precisar desse momento.

Os efeitos são declarados na ordem efeito, layout effect, mas o console imprime o layout effect primeiro em toda renderização. A ordem de declaração não importa entre os dois hooks; o momento de cada um, sim.

A sintaxe

A assinatura é idêntica à do useEffect: uma função de setup, uma limpeza opcional e um array de dependências.

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

Tudo o que você sabe sobre dependências e limpeza do useEffect vale aqui. A única diferença é quando o React o chama.

Quando cada um executa

Depois de uma renderização, o React atualiza o DOM e então faz três coisas em uma ordem fixa:

  1. Executa todas as limpezas de layout effects e os layout effects, de forma síncrona.
  2. Deixa o navegador pintar a tela. Se um layout effect definiu estado, o React primeiro renderiza de novo, ainda antes da pintura.
  3. Executa os efeitos comuns, normalmente depois da pintura.

Em uma árvore, todo layout effect executa antes de qualquer efeito comum, e os filhos executam antes dos pais, já que um pai só pode terminar quando os filhos estão no lugar:

O console mostra primeiro os dois layout effects, Child e depois Parent, e em seguida os dois efeitos comuns na mesma ordem. A key={n} faz cada clique montar um Parent novo, então você recebe as mesmas quatro linhas toda vez. Adicione uma limpeza que registre algo em cada efeito e veja os antigos executarem antes dos novos.

O problema da piscada

Imagine um tooltip que deve aparecer acima do botão, a menos que não haja espaço acima, caso em que ele vai para baixo. Você não consegue saber se há espaço até o tooltip estar no DOM e você poder medir a altura dele. Então a renderização acontece em duas passadas:

  1. Renderizar o tooltip em algum lugar, para que possa ser medido.
  2. Medi-lo, calcular a posição real, definir o estado e renderizar de novo.

Com useEffect, o navegador pode pintar a tela entre essas passadas, e o usuário vê o tooltip pular da primeira posição para a segunda. Com useLayoutEffect, a segunda passada acontece antes da pintura, então a primeira posição nunca é mostrada.

Passe o mouse sobre o botão de cima: não há espaço acima dele, então o tooltip aparece embaixo. Passe sobre o de baixo e ele aparece acima. O console diz qual lado a medição escolheu. Troque useLayoutEffect por useEffect e passe o mouse de novo: agora o tooltip pode piscar no topo da caixa por um frame antes de se mover. Em uma máquina rápida muitas vezes é rápido demais para perceber, e é exatamente por isso que esse bug vai para produção e depois é relatado por quem usa celulares mais lentos.

offsetHeight, offsetTop e getBoundingClientRect() forçam o navegador a calcular o layout, então lê-los no layout effect dá os valores reais do DOM que o React acabou de escrever.

Medindo um elemento

O mesmo padrão funciona para qualquer tamanho de que a renderização dependa: leia-o com uma ref em um layout effect, guarde-o no estado e renderize com ele.

Clique no botão e a linha da largura atualiza no mesmo frame que o texto. A página nunca mostra um texto novo ao lado de uma largura antiga, porque a segunda renderização acontece antes da pintura. Remova [text] do array de dependências e a medição executa depois de toda renderização, inclusive a que o próprio setWidth causa; ela só para ali porque a largura não muda mais.

Prefira o useEffect

Um layout effect segura a pintura até retornar e, se define estado, até a renderização extra terminar também. Um layout effect lento faz toda atualização da página parecer lenta. Use useLayoutEffect só quando tudo isto for verdade:

  • O efeito lê o layout (tamanho, posição, rolagem).
  • Ele muda o que é renderizado com base nessa leitura.
  • O estado intermediário ficaria visível e pareceria errado.

Todo o resto (busca de dados, assinaturas, timers, logs, analytics) fica no useEffect. Uma regra rápida: comece com useEffect e só troque se conseguir ver uma piscada.

O useEffect normalmente executa depois da pintura, mas nem sempre. Quando a atualização vem de uma ação pontual do usuário, como um clique ou uma tecla, o React pode executar os efeitos antes de o navegador pintar a tela. Esse é um dos motivos pelos quais uma piscada pode aparecer em uma situação e não em outra.

Renderização no servidor

Os efeitos nunca executam durante a renderização no servidor, e o useLayoutEffect também não. O servidor envia o HTML da primeira renderização, e o layout effect só executa depois da hidratação no navegador. O React 18 e versões anteriores avisavam "useLayoutEffect does nothing on the server" quando um componente assim era renderizado no servidor; o React 19 removeu esse aviso.

Se o primeiro frame renderizado no servidor ficaria errado sem a medição, há duas correções comuns: renderizar um fallback neutro (por exemplo, esconder o tooltip até ele ser medido) ou renderizar o componente só no cliente:

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

useInsertionEffect

Existe um terceiro hook de efeito, o useInsertionEffect, que executa antes de qualquer layout effect. Ele existe para bibliotecas de CSS-in-JS que geram tags <style> em tempo de execução: inserir os estilos ali significa que um layout effect que mede um elemento já o vê estilizado. Ele não pode atualizar estado e as refs ainda não estão conectadas, então não tem utilidade em código de aplicação. Se você não está escrevendo uma biblioteca de estilos, não vai precisar dele.

Perguntas frequentes

Qual é a diferença entre useEffect e useLayoutEffect?

O momento. O useLayoutEffect executa de forma síncrona depois que o React mudou o DOM, mas antes de o navegador pintar a tela, então as atualizações de estado dele são aplicadas antes de o usuário ver qualquer coisa. O useEffect normalmente executa depois da pintura. A API e o array de dependências são os mesmos.

Quando devo usar useLayoutEffect?

Quando um efeito lê o layout (um tamanho ou uma posição) e logo em seguida muda o que é renderizado com base nisso, como posicionar um tooltip acima ou abaixo do botão. Sem ele, o usuário pode ver por um frame a primeira posição, a errada.

O useLayoutEffect é ruim para a performance?

Ele impede o navegador de pintar a tela até terminar, junto com qualquer nova renderização que dispare. Um layout effect lento faz a página inteira parecer lenta, então use useEffect a menos que precise evitar uma piscada visível.

O useLayoutEffect executa no servidor?

Não. Nenhum dos dois efeitos executa durante a renderização no servidor. Versões antigas do React imprimiam um aviso quando um componente com useLayoutEffect era renderizado no servidor; o React 19 não faz mais isso, mas o efeito continua executando só no navegador.

Ilustração das linguagens de programação do Coddy

Aprenda a programar com o Coddy

COMEÇAR