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 faça commit, o React executará a sua função de configuração. Após cada commit com as 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 commit 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
useLayoutEffecte 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. -
If you trigger a state update inside
useLayoutEffect, React will execute all remaining Effects immediately includinguseEffect.
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
Tooltiprenderiza 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
useLayoutEffectmede a altura do conteúdo da ferramenta de dica e altera o valor do state, desencadeando uma nova renderização imediatamente. - O
Tooltiprenderiza novamente com o state (estado)tooltipHeightcontendo 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
useLayoutEffectpelouseEffect. 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
useLayoutEffectsomente após a hidratação. Mantenha um state (estado) booleanoisMountedque é iniciado comofalse, e defina-o comotruedentro 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áFallbackContentque não deve chamar ouseLayoutEffect. Então, o React substituirá isso porRealContentque é 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
useLayoutEffectpor razões diferentes da medição de layout, considere usaruseSyncExternalStoreque oferece suporte a renderização no lado servidor.