useEffect é um Hook do React que permite sincronizar um componente com um sistema externo.

useEffect(configurar, dependências?)

Referência

useEffect(configurar, dependências?)

Chame useEffect no nível superior do seu componente para declarar um Efeito:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}

Veja mais exemplos abaixo.

Parâmetros

  • configurar: A função com a lógica do seu Efeito. Sua função de configuração também pode opcionalmente retornar uma função de limpeza. Quando seu componente é adicionado ao DOM, o React executará sua função de configuração. Após cada re-renderização com dependências alteradas, o React primeiro executará a função de limpeza (se você a forneceu) com os valores antigos e, em seguida, executará sua função de configuração com os novos valores. Após seu componente ser removido do DOM, o React executará sua função de limpeza.

  • opcional dependências: A lista de todos os valores reativos referenciados dentro do código da configurar. Valores reativos incluem props, estado, e todas as variáveis e funções declaradas diretamente dentro do corpo do seu componente. Se seu linter estiver configurado para 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 em linha como [dep1, dep2, dep3]. O React comparará cada dependência com seu valor anterior usando a comparação Object.is. Se você omitir este argumento, seu Efeito será executado novamente após cada re-renderização do componente. Veja a diferença entre passar um array de dependências, um array vazio, e nenhuma dependência.

Retorna

useEffect retorna undefined.

ressalvas

  • useEffect é um Hook, então você só pode chamá-lo no nível superior do seu componente ou de seus próprios Hooks. Você não pode chamá-lo dentro de loops ou condições. Se você precisar disso, extraia um novo componente e mova o estado para ele.

  • Se você não está tentando sincronizar com algum sistema externo, provavelmente você não precisa de um Efeito.

  • Quando o Modo Estrito está ativado, o React executará um ciclo extra de configuração+limpeza apenas para desenvolvimento antes da primeira configuração real. Este é um teste de estresse que garante que a lógica de limpeza “reflete” a lógica de configuração e que ela para ou reverte o que a configuração está fazendo. Se isso causar um problema, implemente a função de limpeza.

  • Se algumas das suas dependências forem objetos ou funções definidas dentro do componente, há o risco de que elas façam o Efeito ser executado novamente mais frequentemente do que o necessário. Para corrigir isso, remova as dependências de objeto e de função desnecessárias. Você também pode extrair atualizações de estado e lógica não reativa para fora de seu Efeito.

  • Se seu Efeito não foi causado por uma interação (como um clique), o React geralmente permitirá que o navegador pinte a tela atualizada primeiro antes de executar seu Efeito. Se seu Efeito estiver fazendo algo visual (por exemplo, posicionando uma dica), e o atraso for perceptível (por exemplo, ele pisca), substitua useEffect por useLayoutEffect.

  • Se seu Efeito foi causado por uma interação (como um clique), o React pode executar seu Efeito antes que o navegador pinte a tela atualizada. Isso garante que o resultado do Efeito possa ser observado pelo sistema de eventos. Normalmente, isso funciona como esperado. No entanto, se você precisar adiar o trabalho até depois da pintura, como um alert(), você pode usar setTimeout. Veja reactwg/react-18/128 para mais informações.

  • Mesmo que seu Efeito tenha sido causado por uma interação (como um clique), o React pode permitir que o navegador repinte a tela antes de processar as atualizações de estado dentro do seu Efeito. Normalmente, isso funciona como esperado. No entanto, se você precisar bloquear o navegador de repintar a tela, você precisa substituir useEffect por useLayoutEffect.

  • Efeitos somente são executados no cliente. Eles não são executados durante a renderização do servidor.


Uso

Conectando-se a um sistema externo

Alguns componentes precisam permanecer conectados à rede, a alguma API do navegador ou a uma biblioteca de terceiros, enquanto são exibidos na página. Esses sistemas não são controlados pelo React, então são chamados de externos.

Para conectar seu componente a algum sistema externo, chame useEffect no nível superior do seu componente:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}

Você precisa passar dois argumentos para useEffect:

  1. Uma função de configuração com código de configuração que conecta a esse sistema.
    • Deve retornar uma função de limpeza com código de limpeza que desconecta desse sistema.
  2. Uma lista de dependências incluindo cada valor do seu componente usado dentro dessas funções.

O React chama suas funções de configuração e limpeza sempre que é necessário, o que pode acontecer várias vezes:

  1. Seu código de configuração é executado quando seu componente é adicionado à página (montado).
  2. Após cada re-renderização do seu componente onde as dependências mudaram:
    • Primeiro, seu código de limpeza roda com os antigos props e estado.
    • Então, seu código de configuração roda com os novos props e estado.
  3. Seu código de limpeza é executado mais uma vez após seu componente ser removido da página (desmontado).

Vamos ilustrar essa sequência pelo exemplo acima.

Quando o componente ChatRoom acima é adicionado à página, ele se conectará à sala de chat com o serverUrl e roomId iniciais. Se serverUrl ou roomId mudarem como resultado de uma re-renderização (por exemplo, se o usuário escolher uma sala de chat diferente em um dropdown), seu Efeito irá desconectar da sala anterior e conectar à próxima. Quando o componente ChatRoom for removido da página, seu Efeito irá se desconectar mais uma vez.

Para ajudar você a encontrar bugs, durante o desenvolvimento, o React executa configuração e limpeza uma vez extra antes da configuração. Este é um teste de estresse que verifica se a lógica do seu Efeito está implementada corretamente. Se isso causar problemas visíveis, sua função de limpeza pode estar faltando alguma lógica. A função de limpeza deve parar ou reverter o que a função de configuração estava fazendo. A regra prática é que o usuário não deve ser capaz de distinguir entre a configuração sendo chamada uma vez (como na produção) e uma sequência de configuraçãolimpezaconfiguração (como no desenvolvimento). Veja soluções comuns.

Tente escrever cada Efeito como um processo independente e pense em um único ciclo de configuração/limpeza de cada vez. Não deve importar se seu componente está montando, atualizando ou desmontando. Quando sua lógica de limpeza reflete corretamente a lógica de configuração, seu Efeito é resiliente a executar configuração e limpeza tantas vezes quanto necessário.

Note

Um Efeito permite que você mantenha seu componente sincronizado com algum sistema externo (como um serviço de chat). Aqui, sistema externo significa qualquer parte do código que não é controlada pelo React, como:

Se você não está se conectando a nenhum sistema externo, provavelmente você não precisa de um Efeito.

Exemplos de conexão a um sistema externo

Example 1 of 5:
Conectando a um servidor de chat

Neste exemplo, o componente ChatRoom usa um Efeito para permanecer conectado a um sistema externo definido em chat.js. Pressione “Abrir chat” para fazer o componente ChatRoom aparecer. Este sandbox é executado em modo de desenvolvimento, então há um ciclo extra de conectar e desconectar, como explicado aqui. Tente mudar o roomId e o serverUrl usando o dropdown e a entrada, e veja como o Efeito reconecta ao chat. Pressione “Fechar chat” para ver o Efeito se desconectar uma última vez.

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, [roomId, serverUrl]);

  return (
    <>
      <label>
        URL do Servidor:{' '}
        <input
          value={serverUrl}
          onChange={e => setServerUrl(e.target.value)}
        />
      </label>
      <h1>Bem-vindo à sala {roomId}!</h1>
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  const [show, setShow] = useState(false);
  return (
    <>
      <label>
        Escolha a sala de chat:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">geral</option>
          <option value="travel">viagem</option>
          <option value="music">música</option>
        </select>
      </label>
      <button onClick={() => setShow(!show)}>
        {show ? 'Fechar chat' : 'Abrir chat'}
      </button>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId} />}
    </>
  );
}


Encapsulando Efeitos em Hooks personalizados

Os Efeitos são um “escape hatch”: que você usa quando precisa “sair do React” e quando não há solução interna melhor para o seu caso de uso. Se você se vê frequentemente precisando escrever Efeitos manualmente, geralmente é um sinal de que você precisa extrair alguns Hooks personalizados para comportamentos comuns dos quais seus componentes dependem.

Por exemplo, este Hook personalizado useChatRoom “oculta” a lógica do seu Efeito atrás de uma API mais declarativa:

function useChatRoom({ serverUrl, roomId }) {
useEffect(() => {
const options = {
serverUrl: serverUrl,
roomId: roomId
};
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId, serverUrl]);
}

Então você pode usá-lo de qualquer componente assim:

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');

useChatRoom({
roomId: roomId,
serverUrl: serverUrl
});
// ...

Existem também muitos ótimos Hooks personalizados para todos os propósitos disponíveis no ecossistema React.

Saiba mais sobre encapsular Efeitos em Hooks personalizados.

Exemplos de encapsulamento de Efeitos em Hooks personalizados

Example 1 of 3:
Hook personalizado useChatRoom

Este exemplo é idêntico a um dos exemplos anteriores, mas a lógica é extraída para um Hook personalizado.

import { useState } from 'react';
import { useChatRoom } from './useChatRoom.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

  useChatRoom({
    roomId: roomId,
    serverUrl: serverUrl
  });

  return (
    <>
      <label>
        URL do Servidor:{' '}
        <input
          value={serverUrl}
          onChange={e => setServerUrl(e.target.value)}
        />
      </label>
      <h1>Bem-vindo à sala {roomId}!</h1>
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  const [show, setShow] = useState(false);
  return (
    <>
      <label>
        Escolha a sala de chat:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">geral</option>
          <option value="travel">viagem</option>
          <option value="music">música</option>
        </select>
      </label>
      <button onClick={() => setShow(!show)}>
        {show ? 'Fechar chat' : 'Abrir chat'}
      </button>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId} />}
    </>
  );
}


Controlando um widget não-React

Às vezes, você deseja manter um sistema externo sincronizado com alguma prop ou estado do seu componente.

Por exemplo, se você tiver um widget de mapa de terceiros ou um componente reprodutor de vídeo escrito sem React, pode usar um Efeito para chamar métodos nele que fazem seu estado corresponder ao estado atual do seu componente React. Esse Efeito cria uma instância da classe MapWidget definida em map-widget.js. Quando você muda a prop zoomLevel do componente Map, o Efeito chama o setZoom() na instância da classe para mantê-la sincronizada:

import { useRef, useEffect } from 'react';
import { MapWidget } from './map-widget.js';

export default function Map({ zoomLevel }) {
  const containerRef = useRef(null);
  const mapRef = useRef(null);

  useEffect(() => {
    if (mapRef.current === null) {
      mapRef.current = new MapWidget(containerRef.current);
    }

    const map = mapRef.current;
    map.setZoom(zoomLevel);
  }, [zoomLevel]);

  return (
    <div
      style={{ width: 200, height: 200 }}
      ref={containerRef}
    />
  );
}

Neste exemplo, uma função de limpeza não é necessária porque a classe MapWidget gerencia apenas o nó DOM que foi passado para ela. Depois que o componente Map do React é removido da árvore, tanto o nó DOM quanto a instância da classe MapWidget serão automaticamente coletados pelo mecanismo JavaScript do navegador.


Buscando dados com Efeitos

Você pode usar um Efeito para buscar dados para seu componente. Note que se você usar uma framework, usar o mecanismo de busca de dados da sua framework será muito mais eficiente do que escrever Efeitos manualmente.

Se você quiser buscar dados de um Efeito manualmente, seu código pode ser assim:

import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';

export default function Page() {
const [person, setPerson] = useState('Alice');
const [bio, setBio] = useState(null);

useEffect(() => {
let ignore = false;
setBio(null);
fetchBio(person).then(result => {
if (!ignore) {
setBio(result);
}
});
return () => {
ignore = true;
};
}, [person]);

// ...

Note a variável ignore que é inicializada como false, e é definida como true durante a limpeza. Isso garante que seu código não sofra de “condições de corrida”: as respostas de rede podem chegar em uma ordem diferente da que você as enviou.

import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';

export default function Page() {
  const [person, setPerson] = useState('Alice');
  const [bio, setBio] = useState(null);
  useEffect(() => {
    let ignore = false;
    setBio(null);
    fetchBio(person).then(result => {
      if (!ignore) {
        setBio(result);
      }
    });
    return () => {
      ignore = true;
    }
  }, [person]);

  return (
    <>
      <select value={person} onChange={e => {
        setPerson(e.target.value);
      }}>
        <option value="Alice">Alice</option>
        <option value="Bob">Bob</option>
        <option value="Taylor">Taylor</option>
      </select>
      <hr />
      <p><i>{bio ?? 'Carregando...'}</i></p>
    </>
  );
}

Você também pode reescrever usando a sintaxe async / await, mas ainda precisa fornecer uma função de limpeza:

import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';

export default function Page() {
  const [person, setPerson] = useState('Alice');
  const [bio, setBio] = useState(null);
  useEffect(() => {
    async function startFetching() {
      setBio(null);
      const result = await fetchBio(person);
      if (!ignore) {
        setBio(result);
      }
    }

    let ignore = false;
    startFetching();
    return () => {
      ignore = true;
    }
  }, [person]);

  return (
    <>
      <select value={person} onChange={e => {
        setPerson(e.target.value);
      }}>
        <option value="Alice">Alice</option>
        <option value="Bob">Bob</option>
        <option value="Taylor">Taylor</option>
      </select>
      <hr />
      <p><i>{bio ?? 'Carregando...'}</i></p>
    </>
  );
}

Escrever a busca de dados diretamente em Efeitos se torna repetitivo e torna difícil adicionar otimizações como cache e renderização no servidor mais tarde. É mais fácil usar um Hook personalizado—seu próprio ou mantido pela comunidade.

Deep Dive

Quais são boas alternativas à busca de dados em Efeitos?

Escrever chamadas fetch dentro de Efeitos é uma maneira popular de buscar dados, especialmente em aplicativos totalmente do lado do cliente. Este é, no entanto, um abordagem muito manual e tem desvantagens significativas:

  • Efeitos não são executados no servidor. Isso significa que o HTML gerado inicialmente no servidor só incluirá um estado de carregamento sem dados. O computador cliente terá que baixar todo o JavaScript e renderizar seu aplicativo apenas para descobrir que agora precisa carregar os dados. Isso não é muito eficiente.
  • Buscar diretamente em Efeitos torna fácil criar “cachoeiras de rede”. Você renderiza o componente pai, ele busca alguns dados, renderiza os componentes filhos, e então eles começam a buscar seus dados. Se a rede não for muito rápida, isso é significativamente mais lento do que buscar todos os dados em paralelo.
  • Buscar diretamente em Efeitos geralmente significa que você não pré-carrega ou armazena em cache os dados. Por exemplo, se o componente for desmontado e depois montado novamente, ele teria que buscar os dados novamente.
  • Não é muito ergonômico. Há bastante código boilerplate envolvido ao escrever chamadas fetch de uma maneira que não sofra de bugs como condições de corrida.

Esta lista de desvantagens não é específica do React. Ela se aplica a buscar dados ao montar com qualquer biblioteca. Assim como com roteamento, buscar dados não é trivial de fazer bem, então recomendamos as seguintes abordagens:

  • Se você usar uma framework, utilize seu mecanismo de busca de dados integrado. Frameworks modernas de React possuem mecanismos integrados de busca de dados que são eficientes e não sofrem com as armadilhas mencionadas acima.
  • Caso contrário, considere usar ou construir um cache do lado do cliente. Soluções populares de código aberto incluem React Query, useSWR, e React Router 6.4+. Você também pode construir sua própria solução, caso em que usaria Efeitos por baixo dos panos, mas também adicionaria lógica para desduplicar solicitações, armazenar respostas em cache e evitar cachoeiras de rede (pré-carregando dados ou elevando os requisitos de dados para rotas).

Você pode continuar buscando dados diretamente em Efeitos se nenhuma dessas abordagens for adequada a você.


Especificando dependências reativas

Note que você não pode “escolher” as dependências do seu Efeito. Cada valor reativo usado pelo código do seu Efeito deve ser declarado como uma dependência. A lista de dependências do seu Efeito é determinada pelo código circundante:

function ChatRoom({ roomId }) { // Este é um valor reativo
const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // Este também é um valor reativo

useEffect(() => {
const connection = createConnection(serverUrl, roomId); // Este Efeito lê esses valores reativos
connection.connect();
return () => connection.disconnect();
}, [serverUrl, roomId]); // ✅ Portanto, você deve especificá-los como dependências do seu Efeito
// ...
}

Se serverUrl ou roomId mudarem, seu Efeito irá reconectar ao chat usando os novos valores.

Valores reativos incluem props e todas as variáveis e funções declaradas diretamente dentro do seu componente. Como roomId e serverUrl são valores reativos, você não pode removê-los das dependências. Se você tentar omiti-los e seu linter estiver configurado corretamente para o React, o linter sinalizará isso como um erro que você precisa corrigir:

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, []); // 🔴 React Hook useEffect tem dependências ausentes: 'roomId' e 'serverUrl'
// ...
}

Para remover uma dependência, você precisa “provar” ao linter que ela não precisa ser uma dependência. Por exemplo, você pode mover serverUrl para fora do seu componente para provar que ele não é reativo e não mudará em re-renderizações:

const serverUrl = 'https://localhost:1234'; // Não é mais um valor reativo

function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ Todas as dependências declaradas
// ...
}

Agora que serverUrl não é mais um valor reativo (e não pode mudar em uma re-renderização), não precisa ser uma dependência. Se o código do seu Efeito não usar nenhum valor reativo, sua lista de dependências deve ser vazia ([]):

const serverUrl = 'https://localhost:1234'; // Não é mais um valor reativo
const roomId = 'music'; // Não é mais um valor reativo

function ChatRoom() {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, []); // ✅ Todas as dependências declaradas
// ...
}

Um Efeito com dependências vazias não reexecutará quando qualquer um dos props ou estado do seu componente mudar.

Pitfall

Se você tem uma base de código existente, pode ter alguns Efeitos que suprimem o linter assim:

useEffect(() => {
// ...
// 🔴 Evitar suprimir o linter assim:
// eslint-ignore-next-line react-hooks/exhaustive-deps
}, []);

Quando as dependências não correspondem ao código, há um alto risco de introduzir bugs. Ao suprimir o linter, você “mente” para o React sobre os valores dos quais seu Efeito depende. Em vez disso, prove que eles são desnecessários.

Exemplos de passagem de dependências reativas

Example 1 of 3:
Passando um array de dependências

Se você especificar as dependências, seu Efeito será executado após a renderização inicial e após re-renderizações com dependências alteradas.

useEffect(() => {
// ...
}, [a, b]); // Executa novamente se a ou b forem diferentes

No exemplo abaixo, serverUrl e roomId são valores reativos, então ambos devem ser especificados como dependências. Como resultado, selecionar uma sala diferente no dropdown ou editar a entrada da URL do servidor faz com que o chat reconecte. No entanto, como message não é usado no Efeito (e, portanto, não é uma dependência), editar a mensagem não reconecta ao chat.

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');
  const [message, setMessage] = useState('');

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, [serverUrl, roomId]);

  return (
    <>
      <label>
        URL do Servidor:{' '}
        <input
          value={serverUrl}
          onChange={e => setServerUrl(e.target.value)}
        />
      </label>
      <h1>Bem-vindo à sala {roomId}!</h1>
      <label>
        Sua mensagem:{' '}
        <input value={message} onChange={e => setMessage(e.target.value)} />
      </label>
    </>
  );
}

export default function App() {
  const [show, setShow] = useState(false);
  const [roomId, setRoomId] = useState('general');
  return (
    <>
      <label>
        Escolha a sala de chat:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">geral</option>
          <option value="travel">viagem</option>
          <option value="music">música</option>
        </select>
      </label>
      <button onClick={() => setShow(!show)}>
        {show ? 'Fechar chat' : 'Abrir chat'}
      </button>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId}/>}
    </>
  );
}


Atualizando o estado baseado em estado anterior a partir de um Efeito

Quando você deseja atualizar o estado com base no estado anterior a partir de um Efeito, você pode encontrar um problema:

function Counter() {
const [count, setCount] = useState(0);

useEffect(() => {
const intervalId = setInterval(() => {
setCount(count + 1); // Você quer incrementar o contador a cada segundo...
}, 1000)
return () => clearInterval(intervalId);
}, [count]); // 🚩 ... mas especificar `count` como uma dependência redefine sempre o intervalo.
// ...
}

Como count é um valor reativo, deve ser especificado na lista de dependências. No entanto, isso faz com que o Efeito limpe e configure novamente toda vez que o count muda. Isso não é ideal.

Para corrigir isso, passe o atualizador de estado c => c + 1 para setCount:

import { useState, useEffect } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setCount(c => c + 1); // ✅ Passe um atualizador de estado
    }, 1000);
    return () => clearInterval(intervalId);
  }, []); // ✅ Agora count não é uma dependência

  return <h1>{count}</h1>;
}

Agora que você está passando c => c + 1 em vez de count + 1, seu Efeito não precisa mais depender de count. Como resultado dessa correção, ele não precisará limpar e configurar o intervalo novamente toda vez que o count mudar.


Removendo dependências de objeto desnecessárias

Se seu Efeito depende de um objeto ou uma função criada durante a renderização, ele pode rodar mais frequentemente. Por exemplo, este Efeito reconecta após cada renderização porque o objeto options é diferente a cada renderização:

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

const options = { // 🚩 Este objeto é criado do zero a cada re-renderização
serverUrl: serverUrl,
roomId: roomId
};

useEffect(() => {
const connection = createConnection(options); // É usado dentro do Efeito
connection.connect();
return () => connection.disconnect();
}, [options]); // 🚩 Como resultado, essas dependências estão sempre diferentes em re-renderizações
// ...

Evite usar um objeto criado durante a renderização como dependência. Em vez disso, crie o objeto dentro do Efeito:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    const options = {
      serverUrl: serverUrl,
      roomId: roomId
    };
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return (
    <>
      <h1>Bem-vindo à sala {roomId}!</h1>
      <input value={message} onChange={e => setMessage(e.target.value)} />
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  return (
    <>
      <label>
        Escolha a sala de chat:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">geral</option>
          <option value="travel">viagem</option>
          <option value="music">música</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

Agora que você cria o objeto options dentro do Efeito, o próprio Efeito depende apenas da string roomId.

Com essa correção, digitar na entrada não reconecta o chat. Ao contrário de um objeto que é recriado, uma string como roomId não muda a menos que você a defina para outro valor. Leia mais sobre remoção de dependências.


Removendo dependências de função desnecessárias

Se seu Efeito depende de um objeto ou uma função criada durante a renderização, ele pode rodar mais frequentemente. Por exemplo, este Efeito reconecta após cada renderização porque a função createOptions é diferente a cada renderização:

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

function createOptions() { // 🚩 Esta função é criada do zero a cada re-renderização
return {
serverUrl: serverUrl,
roomId: roomId
};
}

useEffect(() => {
const options = createOptions(); // É usada dentro do Efeito
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // 🚩 Como resultado, essas dependências estão sempre diferentes em re-renderizações
// ...

Por si só, criar uma função do zero a cada re-renderização não é um problema. Você não precisa otimizar isso. No entanto, se você a usar como uma dependência do seu Efeito, fará com que seu Efeito re-execute após cada re-renderização.

Evite usar uma função criada durante a renderização como dependência. Em vez disso, declare-a dentro do Efeito:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    function createOptions() {
      return {
        serverUrl: serverUrl,
        roomId: roomId
      };
    }

    const options = createOptions();
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return (
    <>
      <h1>Bem-vindo à sala {roomId}!</h1>
      <input value={message} onChange={e => setMessage(e.target.value)} />
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  return (
    <>
      <label>
        Escolha a sala de chat:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">geral</option>
          <option value="travel">viagem</option>
          <option value="music">música</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

Agora que você definiu a função createOptions dentro do Efeito, o Efeito em si depende apenas da string roomId. Com essa correção, digitar na entrada não reconecta o chat. Ao contrário de uma função que é recriada, uma string como roomId não muda, a menos que você a defina para outro valor. Leia mais sobre remoção de dependências.


Lendo os últimos props e estado de um Efeito

Under Construction

Esta seção descreve uma API experimental que ainda não foi lançada em uma versão estável do React.

Por padrão, quando você lê um valor reativo de um Efeito, precisa adicioná-lo como uma dependência. Isso garante que seu Efeito “reaja” a cada mudança desse valor. Para a maioria das dependências, esse é o comportamento que você deseja.

No entanto, às vezes você quer ler os últimos props e estado de um Efeito sem “reagir” a eles. Por exemplo, imagine que você quer registrar o número de itens no carrinho de compras para cada visita à página:

function Page({ url, shoppingCart }) {
useEffect(() => {
logVisit(url, shoppingCart.length);
}, [url, shoppingCart]); // ✅ Todas as dependências declaradas
// ...
}

E se você quiser registrar uma nova visita à página após cada mudança de url, mas não se apenas o shoppingCart mudar? Você não pode excluir shoppingCart das dependências sem quebrar as regras de reatividade. No entanto, você pode expressar que não quer que um trecho de código “reaja” a alterações, mesmo que ele seja chamado de dentro de um Efeito. Declare um Evento de Efeito com o Hook useEffectEvent, e mova o código que lê shoppingCart para dentro dele:

function Page({ url, shoppingCart }) {
const onVisit = useEffectEvent(visitedUrl => {
logVisit(visitedUrl, shoppingCart.length)
});

useEffect(() => {
onVisit(url);
}, [url]); // ✅ Todas as dependências declaradas
// ...
}

Eventos de Efeito não são reativos e devem sempre ser omitidos das dependências do seu Efeito. Isso permite que você coloque código não reativo (onde você pode ler o valor mais recente de alguns props e estado) dentro deles. Ao ler shoppingCart dentro de onVisit, você garante que shoppingCart não reexecutará seu Efeito.

Leia mais sobre como Eventos de Efeito permitem separar código reativo e não reativo.


Exibindo conteúdo diferente no servidor e no cliente

Se seu aplicativo usa renderização do servidor (diretamente ou por meio de uma framework), seu componente será renderizado em dois ambientes diferentes. No servidor, ele renderizará para produzir o HTML inicial. No cliente, o React executará o código de renderização novamente para que possa anexar seus manipuladores de eventos a esse HTML. É por isso que, para hidratação funcionar, sua saída de renderização inicial deve ser idêntica no cliente e no servidor.

Em raras ocasiões, você pode precisar exibir conteúdo diferente no cliente. Por exemplo, se seu aplicativo ler alguns dados de localStorage, não é possível fazer isso no servidor. Aqui está como você poderia implementar isso:

function MyComponent() {
const [didMount, setDidMount] = useState(false);

useEffect(() => {
setDidMount(true);
}, []);

if (didMount) {
// ... retornar JSX apenas do cliente ...
} else {
// ... retornar JSX inicial ...
}
}

Enquanto o aplicativo está carregando, o usuário verá a saída de renderização inicial. Então, quando estiver carregado e hidratado, seu Efeito será executado e definirá didMount como true, acionando uma re-renderização. Isso mudará para a saída de renderização apenas do cliente. Efeitos não são executados no servidor, por isso didMount estava false durante a renderização inicial no servidor.

Use esse padrão com moderação. Tenha em mente que usuários com uma conexão lenta verão o conteúdo inicial por bastante tempo—potencialmente, muitos segundos—então você não quer fazer mudanças bruscas na aparência do seu componente. Em muitos casos, você pode evitar essa necessidade mostrando condicionalmente coisas diferentes com CSS.


Solução de Problemas

Meu Efeito é executado duas vezes quando o componente monta

Quando o Modo Estrito está ativado, no desenvolvimento, o React executa a configuração e a limpeza uma vez extra antes da configuração real.

Este é um teste de estresse que verifica se a lógica do seu Efeito está implementada corretamente. Se isso causar problemas visíveis, sua função de limpeza pode estar faltando alguma lógica. A função de limpeza deve parar ou reverter o que a função de configuração estava fazendo. A regra prática é que o usuário não deve ser capaz de distinguir entre a configuração sendo chamada uma vez (como na produção) e uma sequência de configuração → limpeza → configuração (como no desenvolvimento).

Leia mais sobre como isso ajuda a encontrar bugs e como corrigir sua lógica.


Meu Efeito é executado após cada re-renderização

Primeiro, verifique se você não esqueceu de especificar a array de dependências:

useEffect(() => {
// ...
}); // 🚩 Sem array de dependência: re-executa após cada renderização!

Se você especificou a array de dependências, mas seu Efeito ainda re-executa em um loop, isso ocorre porque uma de suas dependências é diferente em cada re-renderização.

Você pode depurar esse problema registrando manualmente suas dependências no console:

useEffect(() => {
// ..
}, [serverUrl, roomId]);

console.log([serverUrl, roomId]);

Você pode então clicar com o botão direito nas arrays de diferentes re-renderizações no console e selecionar “Armazenar como uma variável global” para ambas. Supondo que a primeira tenha sido salva como temp1 e a segunda tenha sido salva como temp2, você pode então usar o console do navegador para verificar se cada dependência em ambas as arrays é a mesma:

Object.is(temp1[0], temp2[0]); // A primeira dependência é a mesma entre as arrays?
Object.is(temp1[1], temp2[1]); // A segunda dependência é a mesma entre as arrays?
Object.is(temp1[2], temp2[2]); // ... e assim por diante para cada dependência ...

Quando você encontra a dependência que é diferente em cada re-renderização, você geralmente pode corrigir isso de uma dessas maneiras:

Como último recurso (se esses métodos não ajudaram), envolva sua criação com useMemo ou useCallback (para funções).


Meu Efeito continua re-executando em um ciclo infinito

Se seu Efeito está rodando em um ciclo infinito, essas duas coisas devem ser verdadeiras:

  • Seu Efeito está atualizando algum estado.
  • Esse estado leva a uma re-renderização, o que faz com que as dependências do Efeito mudem.

Antes de começar a corrigir o problema, pergunte a si mesmo se seu Efeito está se conectando a algum sistema externo (como DOM, rede, um widget de terceiros, etc.). Por que seu Efeito precisa definir estado? Ele está sincronizando com aquele sistema externo? Ou você está tentando gerenciar o fluxo de dados do seu aplicativo com isso?

Se não há sistema externo, considere se remover o Efeito completamente simplificaria sua lógica.

Se você realmente está sincronizando com algum sistema externo, pense por que e sob quais condições seu Efeito deve atualizar o estado. Alguma coisa mudou que afeta a saída visual do seu componente? Se você precisa acompanhar algum dado que não é usado pela renderização, um ref (que não aciona re-renderizações) pode ser mais apropriado. Verifique se seu Efeito não atualiza o estado (e aciona re-renderizações) mais do que o necessário.

Por fim, se seu Efeito estiver atualizando o estado no momento certo, mas ainda assim houver um loop, é porque a atualização desse estado leva a uma das dependências do Efeito mudando. Leia como depurar mudanças de dependência.


Minha lógica de limpeza é executada mesmo que meu componente não tenha sido desmontado

A função de limpeza é executada não apenas durante a desmontagem, mas antes de cada re-renderização com dependências alteradas. Além disso, em desenvolvimento, o React executa a configuração+limpeza uma vez extra imediatamente após o componente montar.

Se você tiver código de limpeza sem lógica correspondente de configuração, isso geralmente é um sinal de alerta:

useEffect(() => {
// 🔴 Evitar: Lógica de limpeza sem lógica de configuração correspondente
return () => {
doSomething();
};
}, []);

Sua lógica de limpeza deve ser “simétrica” à lógica de configuração e deve parar ou desfazer o que a configuração fez:

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);

Saiba como o ciclo de vida do Effect é diferente do ciclo de vida do componente.


Meu Effect faz algo visual, e eu vejo uma cintilação antes dele ser executado

Se o seu Effect precisa bloquear o navegador de pintar a tela, substitua useEffect por useLayoutEffect. Note que isso não deve ser necessário para a grande maioria dos Effects. Você só precisará disso se for crucial executar seu Effect antes da pintura do navegador: por exemplo, para medir e posicionar uma dica de ferramenta antes que o usuário a veja.