Menu

Hook useEffect no React: dependências, limpeza, exemplos

useEffect executa código depois que o React atualizou a tela, para que um componente se sincronize com algo fora do React: um timer, uma assinatura, um event listener. Aprenda os três formatos do array de dependências, a limpeza, os loops infinitos e quando você nem precisa de um efeito.

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

useEffect executa um trecho de código depois que o React renderizou o seu componente e atualizou a tela. Você o usa para manter um componente sincronizado com algo fora do React, como um timer, uma conexão de rede ou um event listener, e controla quando ele executa de novo com um array de dependências.

Abra o console abaixo da prévia: o efeito registra uma vez quando o componente aparece e de novo depois de cada clique. Troque [count] por [] e clique de novo: o efeito fica em silêncio depois do primeiro registro.

A sintaxe

useEffect(() => {
    // setup: runs after the render is on the screen
    return () => {
        // cleanup: runs before the next setup, and on unmount
    };
}, [dependencies]);
  • O primeiro argumento é a função de setup. Ela pode retornar uma função de limpeza (cleanup), ou nada.
  • O segundo argumento é o array de dependências: todos os valores do componente que o setup lê (props, estado e qualquer coisa calculada a partir deles).
  • Chame useEffect no nível superior do componente, como todo hook.

Os efeitos executam depois da renderização

Renderizar é o React chamando a função do seu componente para descobrir o que a tela deve mostrar. Os efeitos esperam esse trabalho terminar e o DOM ser atualizado, então dentro de um efeito a página já mostra os novos valores.

Cada clique registra primeiro a renderização e depois o efeito, e o efeito lê o texto do botão que o React acabou de escrever. É por isso que ler o DOM, iniciar um timer ou conectar a um servidor fica em um efeito e não no corpo do componente: o corpo executa durante a renderização, antes de qualquer coisa estar na tela, e o React pode chamá-lo mais vezes do que você espera.

Os efeitos normalmente executam depois que o navegador pintou a tela. Quando um efeito precisa medir o DOM antes da pintura, use useLayoutEffect, que sempre executa antes da pintura.

O array de dependências: três formatos

O array de dependências decide quando o efeito executa de novo. Há três formatos, e este bloco usa todos eles para você ver a diferença.

Ao carregar, os três efeitos registram. Clique em a + 1 e dois deles executam: o sem array e o que depende de a. Clique em b + 1 e só o sem array executa, porque nada em [] ou [a] mudou.

Array de dependênciasQuando o efeito executa
nenhumDepois de toda renderização
[]Uma vez, depois da primeira renderização (na montagem)
[a, b]Depois da primeira renderização e sempre que a ou b mudou

O React compara cada dependência com o valor anterior usando Object.is. Números, strings e booleanos são comparados pelo valor. Objetos, arrays e funções são comparados pela identidade, o que importa muito (veja abaixo).

Não deixe um valor de fora do array para fazer um efeito executar menos vezes. O efeito continuaria usando o valor de uma renderização antiga. A regra de lint react-hooks/exhaustive-deps pega isso; ela faz parte da configuração de ESLint que o template React do Vite e a configuração de ESLint do Next.js incluem.

Limpeza

Muitos efeitos iniciam algo que precisa ser parado: uma conexão, uma assinatura, um timer. Retorne uma função do efeito e o React a chama em dois momentos: logo antes de o efeito executar de novo com novas dependências e quando o componente sai da tela (desmontagem).

Escolha outra sala: o console mostra disconnect from general e depois connect to music. A conexão antiga sempre fecha antes de a nova abrir. Clique em Hide e a limpeza executa uma última vez, porque ChatRoom foi desmontado.

A função connect aqui faz o papel de um cliente real de chat ou WebSocket. O formato é o mesmo para qualquer um deles: crie no setup, feche na limpeza.

Limpando um timer

Um timer iniciado em um efeito continua rodando depois que o componente se foi, a menos que a limpeza o pare.

Apague a linha clearInterval(id); e depois esconda o timer: tick continua chegando no console vindo de um componente que não existe mais. Cada Show inicia mais um.

Repare em setSeconds((s) => s + 1). O efeito executa uma vez, então um simples setSeconds(seconds + 1) leria seconds da primeira renderização para sempre e pararia em 1. A função de atualização sempre recebe o valor mais recente.

Removendo um event listener

Event listeners em window ou document seguem o mesmo padrão. Passe para removeEventListener a mesma função que você passou para addEventListener:

useEffect(() => {
    function handleKeyDown(e) {
        if (e.key === 'Escape') onClose();
    }
    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
}, [onClose]);

O loop infinito

Um efeito que define um valor de estado do qual ele também depende inicia um loop: o estado muda, o React renderiza, a dependência mudou, o efeito executa e define o estado de novo.

// Never stops: every render runs the effect, every effect causes a render
useEffect(() => {
    setCount(count + 1);
});

// Same loop: count changes, so the effect runs again
useEffect(() => {
    setCount(count + 1);
}, [count]);

O componente renderiza sem parar e a aba fica ocupada. O React não interrompe esse loop: em desenvolvimento ele registra um erro "Maximum update depth exceeded" no console, e em produção não registra nada. Este bloco executa a segunda versão com uma condição de parada, para você ver o loop sem travar a página:

O console registra de render 0 até render 5: seis renderizações para uma montagem, cada uma iniciada pelo efeito. As correções dependem do que você queria:

  • Você queria uma vez só. Use [] e a forma com função de atualização, setCount((c) => c + 1), para que count não seja uma dependência.
  • Você está calculando um valor a partir de outro estado. Não o guarde; calcule-o durante a renderização, como mostra a última seção desta página.
  • Você está reagindo a uma ação do usuário. Coloque o código no event handler.

Objetos e funções como dependências

Um objeto, array ou função criado no corpo do componente é um valor novo a cada renderização, então um efeito que depende dele executa depois de toda renderização, mesmo quando nada dentro dele mudou.

Alternar as cores não tem nada a ver com a busca, mas cada alternância registra search for react de novo. Mova a linha de options para dentro do efeito e troque o array por [query]: as alternâncias ficam em silêncio e só digitar no input executa a busca.

Essa é a correção geral. Crie objetos e funções dentro do efeito que os usa e dependa dos valores primitivos a partir dos quais eles são montados. Quando uma função precisa viver fora do efeito, o useCallback mantém a identidade dela estável entre renderizações.

Por que os efeitos executam duas vezes em desenvolvimento

Com o <StrictMode> ligado (um app novo do Vite envolve a raiz nele, e o App Router do Next.js o liga por padrão), o React faz um teste em desenvolvimento: monta cada componente, executa os efeitos, executa as limpezas e executa os efeitos de novo. Para a sala de chat acima, o console de desenvolvimento mostra:

connect to general
disconnect from general
connect to general

Isso acontece uma vez, na montagem, e só em desenvolvimento. Existe para expor efeitos que estão sem limpeza: se duas execuções seguidas quebram algo, o usuário também quebraria ao sair e voltar. A correção nunca é bloquear a segunda execução com uma ref; é escrever a limpeza. A prévia desta página roda como um build de produção, então você não vai ver a execução dupla aqui.

Talvez você não precise de um efeito

Efeitos servem para sincronizar com sistemas fora do React. Dois usos comuns ficam melhores sem eles.

Valores calculados a partir de props ou estado. Calcule-os durante a renderização. Guardá-los no estado e sincronizar com um efeito custa uma renderização extra, e a primeira dessas renderizações coloca um valor desatualizado no DOM:

Cada clique renderiza WithEffect duas vezes, e a primeira renderização dele ainda mostra o nome antigo. Computed renderiza uma vez com o valor certo.

Código que responde a uma ação do usuário. Enviar um formulário, mostrar um aviso depois de uma compra ou registrar um evento de analytics para um clique fica no event handler. Ali você sabe exatamente o que aconteceu, enquanto um efeito só sabe que algum valor mudou.

useEffectEvent

Às vezes um efeito precisa do valor mais recente de uma prop sem executar de novo quando essa prop muda. Na sala de chat, uma notificação deve usar o theme atual, mas trocar o tema não deve reconectar. O React 19.2 adiciona o useEffectEvent para isso:

import { useEffect, useEffectEvent } from 'react';

function ChatRoom({ roomId, theme }) {
    const onConnected = useEffectEvent(() => {
        showNotification('Connected!', theme); // always the latest theme
    });

    useEffect(() => {
        const connection = createConnection(roomId);
        connection.on('connected', () => onConnected());
        connection.connect();
        return () => connection.disconnect();
    }, [roomId]); // theme is not a dependency
}

Uma função de useEffectEvent serve só para ser chamada dentro de efeitos. Não a passe para outros componentes nem a liste em um array de dependências.

Perguntas frequentes

Quando o useEffect executa?

Depois que o React renderizou o componente e atualizou o DOM, normalmente depois que o navegador pintou a tela. Sem array de dependências ele executa depois de toda renderização, com [] só depois da primeira, e com [a, b] depois da primeira renderização e de qualquer renderização em que a ou b mudou.

O que é a função de limpeza do useEffect?

A função que você retorna do efeito. O React a chama antes de executar o efeito de novo com novas dependências, e mais uma vez quando o componente sai da tela. Use-a para limpar timers, remover event listeners e fechar assinaturas.

Por que meu useEffect executa duas vezes?

Em desenvolvimento, o <StrictMode> monta cada componente, executa os efeitos, faz a limpeza e os executa de novo, para mostrar efeitos que estão sem limpeza. Isso acontece uma vez na montagem e só em desenvolvimento. Builds de produção executam o efeito uma vez.

Por que meu useEffect causa um loop infinito?

O efeito define um valor de estado que também está no array de dependências (ou não tem array nenhum). Definir o estado renderiza de novo, a dependência mudou, então o efeito executa de novo. Use uma função de atualização e remova a dependência, adicione uma condição ou calcule o valor durante a renderização.

O callback do useEffect pode ser async?

Não. O callback precisa retornar nada ou uma função de limpeza, e uma função async retorna uma promise. Defina uma função async dentro do efeito e chame-a, como mostra a página sobre buscar dados.

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

Aprenda a programar com o Coddy

COMEÇAR