useEffect é um hook para React que permite que você sincronize um componente com um sistema externo.

useEffect(setup, dependencies?)

Referência

useEffect(setup, dependencies?)

Execute useEffect na raiz de seu componente para declarar um Effect:

import { 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

  • setup: A função que contém a lógica do seu Effect. A sua função de setup pode também opcionalmente retornar uma função de cleanup. Quando seu componente for adicionado ao DOM pela primeira vez, o React irá executar sua função de setup. Após cada rerenderização com mudança nas dependências, o React irá primeiro executar a função de cleanup (se você a definiu) com os valores antigos, e então executar sua função de setup com os valores novos. Após seu componente ser removido do DOM, o React executará sua função de cleanup uma última vez.

  • opcional dependencies: A lista de todos valores reativos referenciados dentro do código de setup. Valores reativos incluem props, state e todas as variáveis e funções declaradas diretamente dentro do corpo do seu componente. Se seu linter estiver configurado para React, ele irá verificar que todos valores reativos estão corretamente especificados como dependência. A lista de dependências deve conter um número constante de itens e deve ser escrito inline como [dep1, dep2, dep3]. O React irá comparar cada dependência com seu valor anterior usando a comparação Object.is. Se você emitir este argumento, seu Effect irá ser reexecutado a cada rerenderização do componente. Veja a diferença entre passar um array de dependências, um array vazio, e não passar dependências.

Retorno

useEffect retorna undefined.

Ressalvas

  • useEffect é um Hook, então você só pode o chamar na raiz de seu componente ou em seus próprios Hooks. Você não pode o chamar dentro de loops ou condições. Se você precisar fazer isto, extraia um novo componente e mova o state para dentro dele.

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

  • Quando o Strict Mode estiver ativo, o React irá executar um ciclo extra de setup+cleanup somente em modo de desenvolvimento antes o primeiro setup real. Isto é um teste que garante que sua lógica de cleanup “espelha” sua lógica de setup e que ela pára ou desfaz qualquer coisa que o setup esteja fazendo. Se isto causar um problema, implemente a função de cleanup.

  • Se algumas de suas dependências forem objetos ou funções definidas dentro do componente, há um risco de que elas irão fazer com que o Effect seja reexecutado com mais frequência do que o necessário. Para consertar isto, remova objetos e funções desnecessários de suas dependências. Você também pode extrair atualizações de state e lógica não-reativa do seu Effect.

  • Se seu Effect não foi causado por uma interação (como um clique), o React deixará o navegador pintar a tela atualizada antes de executar seu Effect. Caso seu Effect esteja fazendo algo visual (por exemplo, posicionando um tooltip) e o atraso for perceptível (causando, por exemplo, tremulações), substitua useEffect por useLayoutEffect.

  • Mesmo que seu Effect tenha sido causado por uma interação (como um clique), o navegador pode repintar a tela antes de processar atualizações de state dentro de seu Effect. Normalmente, é isto que você quer. No entanto, se você precisar impedir o navegador de repintar a tela, você precisará substituir useEffect por useLayoutEffect.

  • Effects executam somente no cliente. Eles não são executados durante renderizações do lado do servidor.


Uso

Conectando a um sistema externo

Alguns componentes precisam permanecer conectados à rede, APIs do navegador, ou bibliotecas de terceiros enquanto estão sendo exibidos na página. Estes sistemas não são controlados pelo React, então eles são chamados de externos.

Para conectar seu componente a algum sistema externo, chame useEffect na raiz de seu componente:

import { 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 ao useEffect:

  1. Uma função de setup com código de setup que conecta a este sistema.
    • Ela deve retornar uma função de cleanup com código de cleanup que desconecta deste sistema.
  2. Uma lista de dependências incluindo todos valores de seu componente utilizados dentro destas funções.

O React executará suas funções de setup e cleanup quando necessário, o que pode ocorrer múltiplas vezes:

  1. Seu código de setup executa quando seu componente é adicionado à pagina (mounts).
  2. Após cada rerenderização do seu componente onde as dependências sofreram alterações:
    • Primeiro, seu código de cleanup executa com os props e state antigos.
    • Então, seu código de setup executa com os props e state novos.
  3. Seu código de cleanup executa uma última vez depois que seu componente é removido da página (unmounts).

Vamos ilustrar esta sequência para o exemplo acima.

Quando o componente ChatRoom acima é adicionado à página, ele irá conectar com a sala de chat utilizando os valores iniciais de serverUrl e roomId. Se serverUrl ou roomId mudarem como resultado de uma rerenderização (causada, por exemplo, pelo usuário selecionando outra sala de chat numa lista), seu Effect irá desconectar da sala anterior e conectar à próxima. Quando o compoente ChatRoom for removido da página, seu Effect irá desconectar uma última vez.

No modo de desenvolvimento, para ajudar você a encontrar erros, o React executa setup e cleanup uma vez a mais antes de setup. Isto é um teste que verifica se a lógica do seu Effect está implementada corretamente. Caso isto cause problemas, alguma lógica está faltando na sua função de cleanup. A função de cleanup deveria parar e desfazer qualquer coisa que a função de setup estava fazendo. De maneira geral, o usuário não deveria poder diferenciar se o setup está sendo chamado uma só vez (como em produção) ou numa sequência setupcleanupsetup (como em desenvolvimento). Veja soluções comuns.

Tente escrever cada Effect como um processo independente e pense em um ciclo de setup/cleanup por vez. O fato de seu componente estar montando, atualizando ou desmontando não deveria importar. Quando sua lógica de cleanup “espelha” corretamente sua lógica de setup, seu Effect é resiliente o suficiente para rodar setup e cleanup o quanto for preciso.

Note

Um Effect permite que você mantenha seu componente sincronizado com algum sistema externo (como um serviço de chat). Neste contexto, sistema externo significa qualquer trecho de código que não é controlado pelo React, como por exemplo:

Se você não estiver conectando a um sistema externo, você provavelmente não precisa de um Effect.

Exemplos de conexão a um sistema externo

Example 1 of 5:
Conectando a um servidor de chat

Neste exemplo, o componente ChatRoom utiliza um Effect para permanecer conectado a um sistema externo definido em chat.js. Pressione “Abrir chat” para fazer com que o componente ChatRoom apareça. Este sandbox está executando em modo de desenvolvimento, portanto há um ciclo extra que conecta e desconecta, conforme explicado aqui. Experimente alterar roomId e serverUrl utilizando a lista de opções e o campo de texto, e perceba como o Effect reconecta ao chat. Pressione “Fechar chat” para ver o Effect disconectar 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('geral');
  const [show, setShow] = useState(false);
  return (
    <>
      <label>
        Escolha a sala de chat:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="geral">geral</option>
          <option value="viagem">viagem</option>
          <option value="música">música</option>
        </select>
      </label>
      <button onClick={() => setShow(!show)}>
        {show ? 'Fechar chat' : 'Abrir chat'}
      </button>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId} />}
    </>
  );
}


Encapsulando Effects em Hooks customizados

Effects são uma “válvula de escape”: você as usa quando precisa “sair do React” e quando nao há uma solução integrada melhor para seu caso de uso. Se você perceber que tem precisado escrever muitos Effects, isso é geralmente um sinal que você precisa extrair alguns Hooks customizados para comportamentos comuns que seus componentes dependem.

Por exemplo, este Hook customizado useChatRoom “esconde” a lógica do seu Effect 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 utilizá-lo em qualquer componente deste modo:

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

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

Existem também muitos Hooks customizados excelentes para qualquer propósito disponíveis no ecossistema do React.

Aprenda mais sobre encapsular Effects em Hooks customizados.

Exemplos de encapsulamento de Effects em Hooks customizados

Example 1 of 3:
Hook customizado useChatRoom

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

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('geral');
  const [show, setShow] = useState(false);
  return (
    <>
      <label>
        Escolha a sala de chat:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="geral">geral</option>
          <option value="viagem">viagem</option>
          <option value="música">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

Algumas vezes, você precisa manter um sistema externo sincronizado com alguma prop ou state do seu componente.

Por exemplo, se você possui um widget de mapa ou um componente de reprodução de vídeo não escritos em React, você pode utilizar um Effect para executar métodos nele, fazendo com que seu estado corresponda ao estado atual do seu componente React. Este Effect cria uma instância de uma classe MapWidget definida em map-widget.js. Quando você altera a prop zoomLevel do componente Map, o Effect chama o método setZoom() da instância da classe para manter o valor sincronizado:

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 cleanup não é necessária, porque a classe MapWidget controla somente o nó DOM que foi passado para ela. Após a remoção do componente React Map, tanto o nó DOM quanto a instância da classe MapWidget serão automaticamente coletados pela engine JavaScript do navegador.


Buscando dados com Effects

Você pode usar um Effect para buscar dados para seu componente. Note que se você usa um framework, utilizar o mecanismo de busca de dados do seu framework será muito mais eficiente do que escrever Effects manualmente.

Se você quiser buscar dados com um Effect manualmente, precisará de um código parecido com este:

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

// ...

Perceba a variável ignore, que é inicializada como false e então atualizada para true durante o cleanup. Isso garante que seu código não sofra com “race conditions”: respostas da rede podem chegar numa 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 utilizando a sintaxe async / await, mas ainda precisará definir a função de cleanup:

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

Implementar busca de dados diretamente com Effects se torna repetitivo e dificulta a posterior adição de otimizações como caching e renderização do lado do servidor. É mais fácil utilizar um Hook customizado — ou por você ou mantido pela comunidade.

Deep Dive

Quais as melhores alternativas à busca de dados em Effects?

Escrever chamadas fetch dentro de Effects é um jeito pupular de buscar dados, especialmente em aplicações totalmente client-side. Esta é, entretanto, uma abordagem muito manual e possui desvantagens significativas:

  • Effects não executam no servidor. Isto significa que o HTML inicial renderizado pelo servidor irá conter somente o estado de “carregando”, sem os dados. O computador do cliente terá que fazer o download de todo o JavaScript e renderizar sua aplicação para somente então descobrir que precisará buscar mais dados. Isto não é muito eficiente.
  • Buscar dados diretamente dentro de Effects facilita a criação de “network waterfalls”. Você renderiza o componente pai, ele busca alguns dados, renderiza os componentes filho, e então eles começam a buscar seus próprios dados. Se a rede não for muito rápida, isto é significativamente mais devagar do que buscar todos os dados em paralelo.
  • Buscar dados diretamente dentro de Effects normalmente significa que você não pré-carrega nem armazena dados em cache. Por exemplo, se o componente desmontar e então montar de novo, ele teria que buscar os dados novamente.
  • Não é muito ergonômico. Existe muito código de boilerplate envolvido quando escrevemos chamadas fetch evitando problemas como race conditions.

Esta lista de desvantagens não é específica ao React. Ela se aplica à busca de dados ao montar componentes em qualquer biblioteca. Assim como roteamento, busca de dados não é um problema trivial de resolver, portanto recomendamos as seguintes abordagens:

  • Se você usa um framework, utilize os mecanismos de busca de dados integrados a ele. Frameworks React modernos já possuem mecanismos para busca de dados que são eficientes e não sofrem com as desvantagens mencionadas anteriormente.
  • Caso contrário, considere utilizar ou construir um sistema de cache de dados no lado do cliente. Soluções populares de código aberto incluem React Query, useSWR e React Router 6.4+. Você pode construir sua própria solução também, neste caso você utilizaria Effects por debaixo dos panos, mas também adicionaria lógicas para deduplicar chamadas, realizar cache das respostas e evitar network waterfalls (pré-carregando dados ou elevando requisitos de dados para as rotas).

Você pode continuar buscando dados diretamente em Effects se nenhuma destas abordagens lhe servir.


Especificando dependências reativas

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

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

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

Se serverUrl ou roomId forem alterados, seu Effect irá reconectar ao chat utilizando os novos valores.

Valores reativos incluem props e todas as variáveis e funções declaradas diretamente dentro de seu componente. Dado que roomId e serverUrl são valores reativos, você não os pode remover das dependências. Se você tentar omiti-los e seu linter estiver corretamente configurado para React, o linter irá marcar isto como um erro que precisa ser corrigido:

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

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, []); // 🔴 React Hook useEffect has missing dependencies: 'roomId' and '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 de seu componente para provar que o valor não é reativo e não irá ser alterado em rerenderizaçõ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 dependências declaradas
// ...
}

Agora que serverUrl não é mais um valor reativo (e não pode ser alterado em uma rerenderização), ele não precisa mais ser uma dependência. Se o código do seu Effect não utilizar nenhum valor reativo, a lista de dependências deveria 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 dependências declaradas
// ...
}

Um Effect com dependências vazias não é reexecutado mesmo quando as props ou state de qualquer de seus componentes for atualizado.

Pitfall

Se você tiver um codebase existente, você pode ter alguns Effects que suprimem o linter deste modo:

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

Quando as dependências não correspondem ao código, existe um alto risco de introduzir erros. Ao suprimir o linter, você “mente” ao React sobre os valores nos quais o seu Effect depende. Ao invés disto, prove que eles são desnecessários.

Exemplos de passagem de valores reativos

Example 1 of 3:
Passando uma lista de dependências

Se você especificar as dependências, seu Effect executa após a renderização inicial e depois que rerenderizar com dependências atualizadas.

useEffect(() => {
// ...
}, [a, b]); // Executa novamente caso a ou b sejam alterados

No exemplo abaixo, serverUrl e roomId são valores reativos, então eles devem ambos serem especificados como dependências. Como resultado, selecionar um canal diferente na lista ou editar a URL do servidor causará uma reconexão no chat. No entanto, como message não é utilizado no Effect (e então não é uma dependência), editar a mensagem não causará reconexão.

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('geral');
  return (
    <>
      <label>
        Escolha a sala de chat:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="geral">geral</option>
          <option value="viagem">viagem</option>
          <option value="música">música</option>
        </select>
        <button onClick={() => setShow(!show)}>
          {show ? 'Fechar chat' : 'Abrir chat'}
        </button>
      </label>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId}/>}
    </>
  );
}


Atualizando state baseado em valores anteriores num Effect

Quando você precisar atualizar o state baseado em um valor anterior do state dentro de um Effect, você pode ter o seguinte 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 sempre reseta o intervalo.
// ...
}

Dado que count é um valor reativo, ele deve ser especificado na lista de dependências. No entanto, isto faz com que o Effect rode as funções de cleanup e setup novamente a cada vez que count muda. Isto não é ideal.

Para arrumar isto, passe o state updater c => c + 1 a 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 state updater
    }, 1000);
    return () => clearInterval(intervalId);
  }, []); // ✅ Agora count não é uma dependência

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

Agora que você está passando c => c + 1 ao invés de count + 1, seu Effect não precisa mais depender de count. Como resultado desta correção, o intervalo não precisará mais ser limpo e setado toda vez que count atualizar.


Removendo objetos desnecessários das dependências

Se seu Effect depende de um objeto ou uma função criada durante a renderização, ele pode executar com muita frequência. Por exemplo, este Effect reconecta após cada renderização, pois o objeto options é diferente para cada renderização:

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

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

const options = { // 🚩 Este objeto é recriado a cada rerenderização
serverUrl: serverUrl,
roomId: roomId
};

useEffect(() => {
const connection = createConnection(options); // E usado dentro do Effect
connection.connect();
return () => connection.disconnect();
}, [options]); // 🚩 Como resultado, estas dependências são sempre diferentes numa rerenderização
// ...

Evite utilizar objetos criados durante a renderização como dependência. Ao invés disto, crie estes objetos dentro do Effect:

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('geral');
  return (
    <>
      <label>
        Escolha a sala de chat:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="geral">geral</option>
          <option value="viagem">viagem</option>
          <option value="música">música</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

Agora que você criou o objeto options dentro do Effect, o Effect em si depende somente da string roomId.

Com esta correção, escrever no campo de texto não causa reconexão ao chat. Diferentemente de um objeto que é recriado, uma string como roomId não é modificada a não ser que você altere seu valor. Leia mais sobre remoção de dependências.


Removendo funções desnecessárias das dependências

Se seu effect depende de um objeto ou de uma função criados durante a renderização, ele pode executar com muita frequência. Por exemplo, este Effect reconecta após cada renderização, pois a função createOptions é diferente para cada renderização:

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

function createOptions() { // 🚩 Esta função é recriada a cada rerenderização
return {
serverUrl: serverUrl,
roomId: roomId
};
}

useEffect(() => {
const options = createOptions(); // E usada dentro do Effect
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // 🚩 Como resultado, estas dependências são sempre diferentes numa rerenderização
// ...

Recriar uma função a cada rerenderização em si não é um problema. Você não precisa otimizar isto. No entanto, se você a utiliza como uma dependência de seu Effect, isto irá fazer com que seu Effect seja reexecutado a cada rerenderização.

Evite utilizar funções criadas durante a renderização como dependência. Ao invés disto, as declare dentro do Effect:

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('geral');
  return (
    <>
      <label>
        Escolha a sala de chat:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="geral">geral</option>
          <option value="viagem">viagem</option>
          <option value="música">música</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

Agora que você definiu a função createOptions dentro do Effect, o Effect em si depende somente da string roomId. Com esta correção, escrever no campo de texto não causa reconexão ao chat. Diferentemente de uma função que é recriada, uma string como roomId não é modificada a não ser que você altere seu valor. Leia mais sobre remoção de dependências.


Lendo valores atualizados de props e state a partir de um Effect

Under Construction

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

Por padrão, quando você lê um valor reativo de dentro de um Effect, você precisa adicioná-lo como uma dependência. Isto garante que seu Effect “reage” a cada mudança deste valor. Para a maioria das dependências, este é o comportamento que você quer.

No entanto, algumas vezes você irá querer ler o último valor de props e state dentro de um Effect, sem “reagir” a ele. Por exemplo, imagine que você quer logar o número de itens no carrinho de compras a cada visita à página:

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

E se você quiser logar uma visita a uma página nova a cada mudança em url, mas não se somente shoppingCart for atualizado? Você não pode excluir shoppingCart das dependências sem quebrar as regras de reatividade. No entanto, você pode expressar que você não quer que uma parte do código “reaja” a mudanças, mesmo que seja chamado de dentro de um Effect. Declare um Effect Event com o Hook useEffectEvent, e move o código lendo shoppingCart para dentro dele:

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

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

Effect Events não são reativos e devem sempre ser omitidos das dependências de seu Effect. É isto que permite que você coloque código não-reativo (onde você pode ler o último valor de props e state) dentro deles. Ao ler shoppingCart dentro de onVisit, você garante que shoppingCart não reexecutará seu Effect.

Leia mais sobre como Effect Events permitem que você separe código reativo de não-reativo.


Exibindo conteúdos diferentes no servidor e no cliente

Se sua aplicação usa renderização do lado do servidor (tanto diretamente quanto via um framework), seu componente irá renderizar em dois ambientes diferentes. No servidor, ela irá renderizar para produzir o HTML inicial. No cliente, o React irá executar o código de renderização novamente para poder anexar seus event handlers àquele HTML. É por isto que, para que o hydration funcione, o resultado de sua renderização inicial precisa ser idêntico entre servidor e cliente.

Em raros casos, você pode precisar exibir conteúdo diferente no lado do cliente. Por exemplo, se sua aplicação lê algum dado de localStorage, não é possível fazer isto do lado do servidor. Eis um modo de implementar isto:

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

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

if (didMount) {
// ... retorne JSX somente para cliente...
} else {
// ... retorne o JSX inicial...
}
}

Enquanto a aplicação está carregando, o usuário irá ver a saída inicial da renderização. Então, após o carregamento e execução do hydration, seu Effect irá executar e definir didMount como true, causando uma rerenderização. Isto irá alternar para a renderização do lado do cliente. Effects não são executados no servidor, e é por isso que didMount era false durante a renderização inicial do lado do servidor.

Use este modelo com moderação. Lembre-se de que usuários com conexões lentas irão ver o conteúdo inicial por um bom tempo — potencialmente vários segundos — portanto você não vai querer que seu componente altere sua aparência de forma tão drástica. Em vários casos, você pode evitar esta solução utilizando CSS para exibir condicionalmente elementos distintos.


Solução de problemas

Meu Effect roda duas vezes quando o componente monta

Quando o Strict Mode está ativado, em desenvolvimento, o React roda as funções de setup e cleanup uma vez a mais antes da execução verdadeira do setup.

Este é um teste que verifica se a lógica do seu Effect está implementada corretamente. Se isto causar problemas, alguma lógica está faltando na sua função de cleanup. A função de cleanup deveria parar e desfazer qualquer coisa que a função de setup estava fazendo. De maneira geral, o usuário não deveria poder diferenciar se o setup está sendo chamado uma só vez (como em produção) ou numa sequência setupcleanupsetup (como em desenvolvimento).

Leia mais sobre como isto ajuda a encontrar erros e como corrigir sua lógica.


Meu Effect executa após cada rerenderização

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

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

Se você especificou a lista de dependências mas seu Effect ainda reexecuta em loop, é porque uma de suas dependências é diferente em cada rerenderização.

Você pode depurar este problema manualmente logando suas dependências no console:

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

console.log([serverUrl, roomId]);

Você pode então clicar com o botão direito do mouse nas listas de diferentes rerenderizações e selecionar “Store as a global variable” para ambos. Assumindo que o primeiro foi salvo como temp1 e o segundo como temp2, você pode utilizar o console do navegador para verificar se cada dependência em ambas as listas é a mesma:

Object.is(temp1[0], temp2[0]); // A primeira dependência é a mesma em ambas as listas?
Object.is(temp1[1], temp2[1]); // A segunda dependência é a mesma em ambas as listas?
Object.is(temp1[2], temp2[2]); // ... e por aí vai, para cada dependência ...

Quando você encontrar a dependência que difere em rerenderizações, você geralmente pode consertar isto de um destes modos:

Em última instância (se nenhum destes métodos ajudar), encapsule sua criação com useMemo ou useCallback (para funções).


Meu Effect executa em ciclo infinito

Se seu Effect executa em um ciclo infinito, estas duas coisas devem estar acontecendo:

  • Seu Effect está atualizando algum state.
  • Este state causa um rerender, fazendo com que as dependências do Effect mudem.

Antes de começar a arrumar este problema, pergunte a si mesmo se este Effect está se conectando a um sistema externo (como o DOM, a rede, um widget de terceiros, etc.). Por que seu Effect precisa alterar o state? Ele se sincroniza com este sistema externo? Ou você está tentando gerenciar o fluxo de dados da sua aplicação com ele?

Se não há sistema externo, considere se a remoção completa do Effect simplificaria sua lógica.

Se você está genuinamente sincronizando com algum sistema externo, pense sobre a razão e sobre quais condições seu Effect deveria atualizar o state. Algo que afeta a saída visual do seu componente foi alterado? Se você precisa manter controle sobre algum dado que não é utilizado para renderização, um ref (que não causa rerenderizações) pode ser apropriado. Verifique que seu Effect não atualiza o state (e causa rerenderização) mais que o necessário.

Finalmente, se seu Effect está atualizando o state no momento certo, mas ainda há um ciclo, é porque a atualização deste state faz com que as dependências de outro Effect sejam atualizadas.


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

A função de cleanup é executada não somente durante a desmontagem, mas antes de cada rerenderização com dependências atualizadas. Adicionalmente, em desenvolvimento, o React executa um ciclo extra de setup+cleanup imediatamente após a montagem do componente.

Se você possui código de cleanup sem um código de setup correspondente, isto é geralmente um mau sinal:

useEffect(() => {
// 🔴 Evite: lógica de cleanup sem lógica de setup correspondente
return () => {
doSomething();
};
}, []);

Sua lógica de cleanup deveria ser “simétrica” à lógica de setup, e deveria parar ou desfazer qualquer coisa que o setup tenha feito:

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

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


Meu Effect faz algo visual, e vejo tremulações antes de sua execução

  • Se seu Effect não foi causado por uma interação (como um clique), o React deixará o navegador pintar a tela atualizada antes de executar seu Effect. Caso seu Effect esteja fazendo algo visual (por exemplo, posicionando um tooltip) e o atraso for perceptível (causando, por exemplo, tremulações), substitua useEffect por useLayoutEffect.

  • Mesmo que seu Effect tenha sido causado por uma interação (como um clique), o navegador pode repintar a tela antes de processar atualizações de state dentro de seu Effect. Normalmente, é isto que você quer. No entanto, se você precisar impedir o navegador de repintar a tela, você precisará substituir useEffect por useLayoutEffect.

Se seu Effect precisar impedir o navegador de pintar a tela, substitua useEffect por useLayoutEffect. Perceba que isto não deveria ser necessário para a grande maioria dos Effects. Você só precisará disto se for crucial executar seu Effect antes que o navegador pinte a tela: por exemplo, para medir a posição de um tooltip antes que o usuário o veja.