---
title: "O Modelo Mental Definitivo do useEffect — Chega de Bugs, Chega de Adivinhação"
summary: "A maioria dos bugs de useEffect vem de brigar com o linter em vez de entendê-lo. Este artigo constrói o modelo mental do zero — pureza, valores reativos e a lista de dependências — até os avisos finalmente fazerem sentido."
published: 2025-11-28
tags: [react, hooks, useEffect]
lang: pt-BR
canonical: https://victorleao.dev/pt-BR/articles/the-definitive-mental-model-for-useeffect-no-more-bugs-no-more-guessing
---

Se você já ficou intrigado com o famoso aviso `react-hooks/exhaustive-deps` — ou pior, já o silenciou só para seguir em frente — este artigo é o seu ponto de virada.

Ao final deste mergulho profundo, você vai:

- entender completamente o que os efeitos do React _realmente_ são
- aprender a eliminar bugs sutis
- depurar lógica de efeito quebrada com confiança
- evitar closures obsoletas e loops infinitos
- dominar como aplicar otimizações avançadas ao `useEffect`
- rastrear qualquer problema de `useEffect` até a origem e resolvê-lo corretamente

O hook `useEffect` é uma das APIs mais mal compreendidas do React moderno, e até desenvolvedores
experientes cometem erros com ele. Aliás, a **queda do Dashboard da Cloudflare em 12 de setembro de
2025** — como a própria Cloudflare explicou — teve origem no uso incorreto de `useEffect` no
front-end deles.

> Cloudflare — Deep dive into the September 12 dashboard and API outage, 2025.

Por que isso acontece? Principalmente porque a maioria dos tutoriais e cursos explica **como** usar
o hook, mas raramente esclarece **por que ele existe, que problema resolve e qual é o modelo
conceitual por trás do seu comportamento** — abrindo espaço para os usos incorretos mais comuns.

Este artigo constrói um modelo mental claro desde a base, conectando fundamentos de ciência da
computação à arquitetura do React e ao seu fiber scheduler. Vamos começar de onde tudo parte: os
**efeitos colaterais**.

## O Conceito Central: Efeitos Colaterais

Em ciência da computação, uma função tem um **efeito primário**: ler entradas e retornar um valor.

> **Função pura**
>
> Uma função que sempre produz a mesma saída para as mesmas entradas e não toca em nada fora do seu próprio escopo.

Qualquer coisa além disso é um **efeito colateral**, como:

- alterar variáveis externas
- lançar exceções
- realizar I/O
- fazer requisições de rede
- mutações no DOM
- interagir com APIs do navegador

> **Efeito colateral**
>
> Qualquer interação observável que uma função tem com o mundo fora do seu próprio escopo — o oposto da pureza.

Efeitos colaterais não são completamente "ruins", mas tornam o raciocínio mais complexo. É por isso
que o paradigma da Programação Funcional incentiva funções puras: previsíveis, testáveis, fáceis de
compor.

O React também adota a pureza em seu processo de renderização. A documentação oficial é clara:

> O processo de renderização do React deve ser sempre puro. Componentes devem apenas **retornar**
> seu JSX, e não **alterar** nenhum objeto ou variável que já existia antes da renderização — isso
> os tornaria impuros!

A pureza é essencial porque o React pode re-renderizar seus componentes:

- mais vezes do que você espera
- fora de ordem
- em lotes
- em desenvolvimento (double-render do **Strict Mode** — veja [como o StrictMode melhora a qualidade do código](https://medium.com/javascript-in-plain-english/how-react-strictmode-boosts-code-quality-49d3832806af))
- durante transições
- durante retentativas de suspense

O React precisa poder re-executar componentes com segurança a qualquer momento. Mas aplicações web
precisam **interagir com o mundo exterior**. Então, como combinamos pureza com interatividade?

## O propósito dos Effects no React

Event handlers são o primeiro mecanismo natural para efeitos colaterais:

```tsx
<button onClick={handleClick}>Click</button>
```

Handlers são definidos durante a renderização, mas executados **depois da renderização**, e apenas
em resposta a uma interação do usuário. Por isso, eles não quebram a pureza.

Mas às vezes um componente precisa se sincronizar com sistemas externos porque ele renderizou, não
porque o usuário clicou em algo:

- atualizar `document.title`
- interagir com `localStorage`
- anexar event listeners
- gerenciar conexões WebSocket
- rolar até um elemento
- integrar com APIs do navegador

Esses são efeitos colaterais que surgem da fase de renderização do componente, e devem ser
agendados como effects. **É exatamente por isso que o `useEffect` existe.**

O React garante pureza durante a renderização, e agenda os effects para rodar **depois da
renderização**, na fase de commit.

## A mecânica fundamental do `useEffect`

Agora que sabemos o verdadeiro propósito do hook, vamos entender sua sintaxe e execução.

```ts
useEffect(setup, deps?)
```

Ele retorna `undefined` e aceita dois argumentos:

1. `setup`: uma função contendo a lógica do seu Effect, que pode ou não retornar uma **função de
   limpeza** (veremos isso adiante).
2. `deps` (opcional): um **array de dependências**.

O effect executa a função `setup` **depois que o componente renderiza**. Por padrão, ele rodaria a
cada renderização, mas raramente queremos isso — por isso usamos o array de dependências na maior
parte das vezes, mesmo quando ele está vazio. Veja como o effect responde a cada formato desse
argumento:

| Array de Dependências (`deps`) | Comportamento do Effect                                                                          |
| -------------------------------- | --------------------------------------------------------------------------------------------------- |
| **Omitido**                        | Roda depois de **toda** renderização.                                                             |
| `[]` (Array Vazio)                 | Roda o effect só depois da **primeira** renderização, e a limpeza quando o componente **desmonta**. |
| `[var1, var2]`                     | Roda depois da primeira renderização e sempre que `var1` ou `var2` **mudam**.                       |

> **Como o array de dependências é comparado**
>
> A checagem de mudança no array de dependências é feita usando `Object.is()`. Se um objeto, array ou função é recriado a cada renderização — comum quando definido dentro do componente — ele será considerado diferente mesmo que seus campos e valores sejam idênticos, e o effect vai rodar de novo. Isso acontece porque objetos e funções são Tipos por Referência em JavaScript: eles não armazenam um valor, apenas uma referência a um local de memória, que é novo a cada criação.

Um dos erros mais comuns e perigosos é acreditar que podemos preencher o array de dependências como
quisermos. Pelo contrário: a lista de dependências deve ser ditada pelo próprio Effect — ela precisa
conter todo **valor reativo** usado dentro dele: toda prop, estado e função declarados dentro do
componente.

Para evitar bugs causados por uma lista de dependências mal preenchida, nosso maior aliado é um
linter configurado para React. Ele nos diz exatamente como a lista deve ser preenchida, com base no
código presente no nosso Effect. Seus avisos não devem ser suprimidos — pelo contrário: a
documentação oficial do React nos diz para tratá-los como erros de compilação, dado o alto risco de
bugs por uso incorreto do array de dependências. É tentador achar que uma dependência pode
simplesmente ser removida; na prática, removê-la exige alterar o código ao redor para "provar" ao
linter que aquele valor deixou de ser reativo.

> Ajuda manter em mente uma espécie de Princípio de Responsabilidade Única para effects: cada effect deve lidar com um único processo de sincronização. Misturar responsabilidades diferentes em um Effect causa dependências desnecessárias, renderizações extras e sincronização no momento errado.

Antes de partirmos para remover dependências corretamente, vamos fechar a explicação da sintaxe do
`useEffect` com sua última peça, não menos importante: **a função de limpeza**.

Se um Effect roda múltiplas vezes — criando inscrições ou listeners, por exemplo — precisamos de uma
forma de cancelar a inscrição anterior ou remover o listener anterior, ou corremos o risco de
vazamentos de memória e outros comportamentos inesperados. Para isso, a função `setup` pode
_retornar_ outra função: a função de limpeza.

```ts
useEffect(() => {
  // código de setup
  return () => {
    // Esta é a função de limpeza.
    // Ela roda para "desfazer" o setup anterior.
  }
}, [deps])
```

A função de limpeza roda (1) antes de cada nova execução do Effect, a partir da segunda, e (2) uma
última vez quando o componente é desmontado — garantindo que o effect anterior seja encerrado
corretamente antes que um novo comece.

## Estratégias para remover dependências do Effect

Às vezes uma lista de dependências parece errada, ou um valor parece mudar com mais frequência do
que deveria — sendo recriado a cada renderização, por exemplo. Isso pode realmente acontecer, mas
uma dependência nunca pode ser removida abruptamente sem alterar o código ao redor de acordo.

O primeiro passo é verificar se o Effect realmente é um Effect, ou se deveria ser um Event Handler:

> **Effect**
>
> Um efeito colateral causado pela renderização do componente, usado para sincronizar o componente com um sistema externo ao React. Ele roda depois do processo de renderização, preservando a pureza desse processo.

> **Event Handler**
>
> Lógica disparada por uma interação específica do usuário — um clique, um scroll, o movimento do mouse sobre um componente, e assim por diante.

> Por convenção, nomes de event handlers seguem o formato `handle[EventName]`, e props para eventos customizados nos seus componentes seguem o padrão `on[EventName]`.

Depois de ter certeza de que a lógica realmente pertence ao `useEffect`, verifique se ela realiza
apenas **um** processo de sincronização. Isso evita que dependências de um processo diferente
causem re-execuções desnecessárias.

Com isso resolvido, voltamos nossa atenção aos valores reativos que o Effect de fato referencia.
Isso revela alguns cenários distintos, cada um com sua própria estratégia de solução. Os três
primeiros tentam evitar objetos e funções dentro do array de dependências.

### Cenário 1: Objetos ou funções sem valores reativos

A primeira coisa a verificar é se essas entidades dependem de algum valor reativo (props ou estado).
Se não dependem, podemos mover sua declaração para fora do escopo do componente, dando a elas uma
referência estável, tratada como não reativa.

![Um componente que recria um objeto de configurações e uma função logSuccess a cada renderização, e os lista como dependências do useEffect](https://victorleao.dev/images/articles/useeffect-mental-model/bad-component-example.png)

*Ruim: o objeto e a função são recriados a cada renderização, então o linter aponta os dois como dependências instáveis.*

Como esperado, o linter reporta um problema tanto na declaração do objeto quanto na da função:

![Aviso do ESLint react-hooks/exhaustive-deps: o objeto DEFAULT_SETTINGS faz as dependências do Effect mudarem a cada renderização](https://victorleao.dev/images/articles/useeffect-mental-model/linter-warning-object.png)

*O react-hooks/exhaustive-deps aponta o objeto: sua identidade muda a cada renderização.*

![Aviso do ESLint react-hooks/exhaustive-deps: a função logSuccess faz as dependências do Effect mudarem a cada renderização](https://victorleao.dev/images/articles/useeffect-mental-model/linter-warning-function.png)

*A mesma regra aponta a função, pelo mesmo motivo.*

Isso mostra por que um linter bem configurado — especificamente a regra `react-hooks/exhaustive-deps`
— é tão importante. Ele sugere duas correções comuns: mover a inicialização para dentro do Effect,
ou envolvê-la em `useMemo()` / `useCallback()`.

Mover a inicialização para dentro do Effect resolve o aviso, mas **adiciona trabalho desnecessário a
cada execução**. A correção com `useMemo()` / `useCallback()` é mais interessante, e vamos detalhá-la
no Cenário 3.

Neste caso específico — objetos e funções que não dependem de valores reativos — a melhor solução é
a mais simples: mover a declaração completamente para fora do escopo do componente.

![O objeto de configurações e a função logSuccess movidos para fora do componente, agora tratados como não reativos pelo linter](https://victorleao.dev/images/articles/useeffect-mental-model/good-component-example.png)

*Bom: definidos uma única vez, fora do componente, eles são não reativos por construção — sem aviso, sem re-execuções desnecessárias.*

### Cenário 2: Usando propriedades específicas de objetos complexos

Quando um Effect precisa apenas de um subconjunto das propriedades de um objeto maior — digamos, uma
prop `user` — depender do objeto inteiro é ineficiente: sua referência muda em quase toda
re-renderização, então o Effect roda de novo sem necessidade. A correção é a **desestruturação**:
extrair apenas os valores primitivos que o Effect realmente precisa, e listar apenas esses no array
de dependências.

```tsx
type GoodComponentExampleProps = {
  settings: Settings
}

function GoodComponentExample({ settings }: GoodComponentExampleProps) {
  const { trackMouse } = settings // ✅ desestrutura o primitivo que precisamos

  useEffect(() => {
    if (!trackMouse) return // ✅

    const handleMouseMove = (event: MouseEvent) => {
      console.log(`posição do mouse: { x: ${event.clientX}, y: ${event.clientY} }`)
    }

    window.addEventListener('mousemove', handleMouseMove)
    return () => window.removeEventListener('mousemove', handleMouseMove)
  }, [trackMouse]) // ✅ não [settings] — depende só do necessário

  return <div>GoodComponentExample</div>
}
```

Se o Effect dependesse do objeto `settings` inteiro, ele rodaria toda vez que qualquer uma de suas
propriedades mudasse. Ao desestruturar e depender apenas do primitivo `trackMouse`, evitamos isso.

### Cenário 3: Referências instáveis criadas durante a renderização

Se nenhuma das estratégias anteriores se aplica, o problema provavelmente é uma **referência
instável**: um objeto literal (`{...}` ou `[...]`) ou uma função declarada diretamente no corpo do
componente. Toda re-renderização cria uma referência de memória totalmente nova para ela, então um
Effect que depende dela roda de novo sem necessidade — e pode até causar um loop infinito.

A estratégia é memoizar o valor para manter sua referência estável entre renderizações:

- `useMemo` para objetos, arrays ou valores calculados.
- `useCallback` para funções.

O `useMemo` executa sua função `calculateValue` e memoiza o valor retornado; o `useCallback` apenas
memoiza a definição da função, garantindo que o Effect sempre receba a mesma referência estável. A
função passada ao `useMemo` roda **durante a renderização**, então precisa ser pura e não receber
argumentos — dependendo inteiramente do seu próprio array de dependências para qualquer valor
reativo que precisar.

### Cenário 4: Lendo um valor reativo sem "reagir" a ele

Às vezes um Effect precisa do **valor mais recente** de uma variável reativa, mas deve rodar de novo
apenas quando uma dependência diferente, não relacionada, mudar. Incluir essa variável no array de
dependências força re-execuções desnecessárias.

A correção certa depende da versão do React:

**React 19.2 ou mais recente — `useEffectEvent`**

A solução moderna é o `useEffectEvent`, que extrai lógica não reativa para fora do Effect. A função
que ele retorna é garantidamente estável — sua referência nunca muda — então ela não precisa entrar
no array de dependências.

```tsx
type ChatProps = {
  roomId: number
  notificationCount: number
}

function Chat({ roomId, notificationCount }: ChatProps) {
  const onVisit = useEffectEvent((visitedRoomId) => {
    console.log(visitedRoomId, notificationCount)
  })

  useEffect(() => {
    onVisit(roomId)
  }, [roomId]) // ✅ todas as dependências declaradas

  return <div>Chat Room {roomId}</div>
}
```

A documentação oficial lista três ressalvas importantes:

> **Ressalvas**
>
> - **Só chame dentro de Effects.** Effect Events só devem ser chamados dentro de Effects. Defina-os
>   logo antes do Effect que os usa; não os passe para outros componentes ou hooks. O linter
>   `eslint-plugin-react-hooks` (6.1.1+) reforça essa regra.
> - **Não é um atalho para dependências.** Não use `useEffectEvent` para evitar especificar
>   dependências — isso esconde bugs e dificulta o entendimento do código. Prefira dependências
>   explícitas, ou refs para comparar valores anteriores quando necessário.
> - **Apenas para lógica não reativa.** Use `useEffectEvent` só para extrair lógica que não depende
>   de valores que mudam.

**Antes do React 19.2 — `useRef` + `useCallback`**

Para versões mais antigas do React, o padrão estabelecido é guardar o valor reativo mais recente em
um `useRef`, lido a partir de um handler `useCallback` estável. Isso exige um `useEffect` separado,
cuja única função é manter a ref atualizada, para que o Effect principal sempre leia o dado atual
sem depender dele.

```tsx
type ChatProps = {
  roomId: number
  notificationCount: number
}

function Chat({ roomId, notificationCount }: ChatProps) {
  const notificationCountRef = useRef(notificationCount)

  useEffect(() => {
    notificationCountRef.current = notificationCount
  }, [notificationCount])

  const onVisit = useCallback((currNotificationCount: number, roomId: number) => {
    console.log(currNotificationCount, roomId)
  }, [])

  useEffect(() => {
    onVisit(notificationCountRef.current, roomId)
  }, [onVisit, roomId])

  return <div>Chat Room {roomId}</div>
}
```

## Uma nota importante sobre busca de dados

Embora tecnicamente possível, a própria **documentação do React desaconselha** usar `useEffect`
para busca de dados em aplicações modernas:

> Escrever chamadas de `fetch` dentro de Effects é uma
> [forma popular de buscar dados](https://www.robinwieruch.de/react-hooks-fetch-data/),
> especialmente em apps totalmente client-side. Essa é, porém, uma abordagem bastante manual, com
> desvantagens significativas:
>
> - **Effects não rodam no servidor.** O HTML renderizado no servidor inclui apenas um estado de
>   carregamento sem dados — o cliente precisa baixar todo o JavaScript e renderizar o app só para
>   descobrir que precisa carregar dados. Pouco eficiente.
> - **Buscar dados diretamente em Effects facilita criar "cascatas de rede."** O componente pai
>   renderiza, busca dados, renderiza os filhos, e só então eles começam a buscar seus próprios
>   dados — bem mais lento do que buscar tudo em paralelo.
> - **Buscar diretamente em Effects geralmente significa sem pré-carregamento ou cache.** Se um
>   componente desmonta e monta de novo, ele busca os dados de novo.
> - **Não é muito ergonômico.** Há bastante código repetitivo para evitar bugs como condições de
>   corrida.
>
> Isso não é específico do React — se aplica a buscar dados na montagem com qualquer biblioteca.
> Recomendamos:
>
> - **Se você usa um framework, use o mecanismo de busca de dados dele.** Frameworks React modernos
>   têm mecanismos de busca integrados e eficientes, que evitam os problemas acima.
> - **Caso contrário, use ou construa um cache client-side.** TanStack Query, useSWR e React Router
>   6.4+ são opções populares. Você também pode construir o seu — nesse caso, ainda usaria Effects
>   por baixo dos panos, além de lógica para deduplicar requisições, cachear respostas e evitar
>   cascatas.

Bibliotecas como essas lidam com sincronização, cache, revalidação e limpeza de forma muito mais
eficiente e robusta do que um `useEffect` feito à mão, abstraindo esse caso de uso específico.

## Considerações finais

Dominar o `useEffect` não é sobre decorar regras — é sobre construir um modelo mental.

Effects existem porque o React precisa manter a renderização pura. Effects rodam depois do commit
para que o React possa se sincronizar com sistemas externos com segurança.

Quando você entende:

- pureza
- valores reativos
- correção do array de dependências
- referências estáveis
- comportamento de limpeza
- padrões de sincronização
- estratégias modernas de busca de dados

...tudo sobre `useEffect` se torna previsível. Você para de brigar com o linter. Você para de cair em
closures obsoletas. Você começa a raciocinar como um engenheiro React sênior.

E o mais importante: **você nunca mais vai suprimir o `react-hooks/exhaustive-deps` só para seguir
em frente.**
