Encontrando os Effects do React de que você não precisa
Use eslint-plugin-react-you-might-not-need-an-effect para detectar efeitos React desnecessários e refatorar estado derivado, manipuladores de eventos e redefinições por props.
eslint-plugin-react-you-might-not-need-an-effect é um plugin para ESLint e Oxlint que sinaliza chamadas de useEffect que a documentação do React considera desnecessárias, como effects que derivam estado, repassam eventos ou enviam dados para um componente pai.
Você leu You Might Not Need an Effect, concordou com tudo e depois abriu uma codebase com quatrocentas chamadas de useEffect e nenhuma forma prática de saber quais delas são o problema.
Este artigo mostra como instalar o plugin, o que ele reporta em um componente realista, como refatorar os quatro apontamentos mais comuns e quando um aviso deve ser suprimido em vez de corrigido. Para relembrar o funcionamento do hook em si, veja este guia sobre o hook useEffect do React.
Principais conclusões
- O eslint-plugin-react-you-might-not-need-an-effect vem com nove regras e dois presets de flat config:
recommendedreporta todas as regras como avisos (warnings), estrictreporta todas as regras como erros. Projetos com .eslintrc legado contam com os presets equivalenteslegacy-recommendedelegacy-strict. - Calcule durante a renderização os valores que dependem apenas das props e do estado atuais, em vez de copiá-los para o estado com um effect.
- Para resetar todo o estado de um componente quando uma prop, como o ID de um cliente, muda, renderize o componente usando essa prop como
key. - Effects que mantêm o React sincronizado com um sistema externo, como um timer, uma API do DOM, um widget que não é React ou uma conexão de rede, são exatamente a razão de existir do
useEffect, portanto mantenha-os. - Suprima um aviso com um comentário
eslint-disable-next-lineque indique a regra e apresente um motivo após--.
O que torna um Effect do React desnecessário?
A maioria dos effects desnecessários aparece em dois formatos: um effect que recalcula um valor e o armazena no estado, ou um effect que observa uma parte do estado e atualiza outra para acompanhá-la. Em ambos os casos, o React faz o commit de uma renderização com o valor antigo, executa o effect e depois renderiza novamente com o valor corrigido.
A renderização extra causada por um effect desnecessário é visível para os usuários. Session replays de componentes repletos de effects podem mostrar uma lista filtrada vazia ou um total provisório durante um frame, antes que a segunda renderização o preencha. A correção quase sempre é remover o estado, e não ajustar o effect.
Instale e execute o plugin
O plugin é instalado como dependência de desenvolvimento e se integra à flat config do ESLint por meio do preset recommended. Se você já gerencia vários plugins do ESLint, ele convive com eles sem conflitos.
npm install --save-dev eslint-plugin-react-you-might-not-need-an-effect
// eslint.config.js
import reactYouMightNotNeedAnEffect from "eslint-plugin-react-you-might-not-need-an-effect";
import globals from "globals";
export default [
reactYouMightNotNeedAnEffect.configs.recommended,
{
languageOptions: {
globals: { ...globals.browser },
parserOptions: { ecmaFeatures: { jsx: true } },
},
},
];
Os exemplos usam a versão 1.0.2, lançada em 20 de agosto de 2026, de acordo com o CHANGELOG do plugin. O mesmo changelog registra que no-pass-ref-to-parent e no-empty-effect foram removidas antes da 1.0, então exclua-as de configurações mais antigas. Quem usa Oxlint carrega o plugin pelo campo jsPlugins do Oxlint. Os IDs das regras usam o prefixo em kebab-case react-you-might-not-need-an-effect/ no Oxlint, em configurações legadas e na flat config quando você usa os presets, porque os presets registram o plugin com o nome do pacote. O prefixo em camelCase reactYouMightNotNeedAnEffect/ só se aplica se você mesmo registrar o plugin com essa chave, como faz o exemplo de regra única do README.
Veja a seguir um componente com quatro effects, cada um escrito para corresponder ao padrão documentado de uma regra:
import { useEffect, useState } from "react";
import { postOrder, useCustomerHistory } from "./api";
export function OrderPanel({ customerId, items, onLoaded }) {
const [total, setTotal] = useState(0);
const [submitted, setSubmitted] = useState(false);
const [note, setNote] = useState("");
const history = useCustomerHistory(customerId);
// Effect 1
useEffect(() => {
setTotal(items.reduce((sum, i) => sum + i.price * i.qty, 0));
}, [items]);
// Effect 2
useEffect(() => {
if (submitted) postOrder(customerId, items, note);
}, [submitted, customerId, items, note]);
// Effect 3
useEffect(() => {
onLoaded(history);
}, [history, onLoaded]);
// Effect 4
useEffect(() => {
setNote("");
setSubmitted(false);
}, [customerId]);
return (
<form onSubmit={(e) => { e.preventDefault(); setSubmitted(true); }}>
<textarea value={note} onChange={(e) => setNote(e.target.value)} />
<p>Total: {total}</p>
<button type="submit">Place order</button>
</form>
);
}
Execute npx eslint src/OrderPanel.jsx. Com o recommended, os apontamentos aparecem como avisos, com o ID da regra no final de cada linha. Cada effect corresponde à regra que ele dispara:
| Effect | Padrão | Regra |
|---|---|---|
| 1 | Estado calculado a partir de props | no-derived-state |
| 2 | Flag de estado usada como gatilho de evento | no-event-handler |
| 3 | Dados buscados repassados a um callback do pai | no-pass-data-to-parent |
| 4 | Estado limpo quando uma prop muda | no-adjust-state-on-prop-change (um aviso por setter) |
O Effect 4 é o caso que a key resolve. A regra no-reset-all-state-on-prop-change reporta esse mesmo formato em componentes mais simples, mas em um componente como este, que lê seu estado durante a renderização, a versão 1.0.2 reporta cada reset sob no-adjust-state-on-prop-change.
O conjunto completo de regras, com a substituição recomendada para cada uma:
| Regra | Substitua o effect por |
|---|---|
no-derived-state | Um valor calculado durante a renderização |
no-chain-state-updates | Uma única atualização de estado consolidada |
no-event-handler | Código no event handler |
no-adjust-state-on-prop-change | Estado reestruturado ou um cálculo em tempo de renderização |
no-reset-all-state-on-prop-change | A prop passada como key |
no-pass-live-state-to-parent | Estado elevado (lifted state) ou um valor retornado por um hook |
no-pass-data-to-parent | Busca de dados no pai, com o resultado passado como props |
no-external-store-subscription | useSyncExternalStore |
no-initialize-state | Um valor inicial em useState() |
O plugin rastreia cadeias de chamadas e arrays de dependências, o que aumenta o trabalho do lint. O profiling de regras do ESLint (TIMING=1) mostra quanto isso custa no seu próprio repositório. O README também sugere habilitar react-hooks/exhaustive-deps, porque o plugin pressupõe que seus arrays de dependências estão corretos, e typescript-eslint/no-floating-promises, que o ajuda a reconhecer chamadas a funções assíncronas.
Estado derivado: calcule durante a renderização
Quando um valor pode ser obtido a partir das props e do estado que você já tem, calcule-o durante a renderização. Não mantenha uma cópia no estado sincronizada por um effect. A documentação do React aborda isso em atualizar o estado com base em props ou estado.
// Before
const [total, setTotal] = useState(0);
useEffect(() => {
setTotal(items.reduce((sum, i) => sum + i.price * i.qty, 0));
}, [items]);
// After
const total = items.reduce((sum, i) => sum + i.price * i.qty, 0);
Agora o total está correto já na primeira renderização. Se o cálculo se mostrar custoso, envolva-o em useMemo. Não o coloque de volta no estado.
Lógica de event handler: mova-a para o handler
Um effect que aguarda uma flag de estado definida em um handler de submit ou de clique é um event handler disfarçado. Execute essa lógica no próprio handler. A justificativa é detalhada em Separating Events from Effects.
// Before
const [submitted, setSubmitted] = useState(false);
useEffect(() => {
if (submitted) postOrder(customerId, items, note);
}, [submitted, customerId, items, note]);
// After
function handleSubmit(e) {
e.preventDefault();
postOrder(customerId, items, note);
}
A versão “antes” tem um bug real: editar a observação depois do envio faz o pedido ser enviado novamente. Desde a versão 0.10.1, no-event-handler também sinaliza props usadas dessa forma e, desde a 0.10.2, reporta no ponto de uso, e não na declaração.
Busca de dados e envio de dados para o pai
Um componente filho que busca dados e os repassa ao pai por meio de um effect deve delegar essa tarefa ao pai, ao carregamento de dados do framework ou a uma biblioteca de data fetching, e receber o resultado como props. O React descreve essa mesma inversão em passar dados para o pai.
// Before: child fetches, then pushes up
const history = useCustomerHistory(customerId);
useEffect(() => {
onLoaded(history);
}, [history, onLoaded]);
// After: parent owns the data
function OrderPage({ customerId, items }) {
const history = useCustomerHistory(customerId); // loader, library, or parent effect
return <OrderPanel customerId={customerId} items={items} history={history} />;
}
Quando os dados fluem em uma única direção, fica claro qual componente é o dono deles, e OrderPanel não precisa mais da prop de callback onLoaded apenas para informar o que carregou.
Resetando o estado quando uma prop muda: use key
Para resetar todo o estado de um componente quando uma prop, como o ID de um cliente, muda, renderize o componente usando essa prop como key, para que o React monte uma nova instância. O padrão está documentado em resetar todo o estado quando uma prop muda.
// Before: Effect 4 inside OrderPanel
useEffect(() => {
setNote("");
setSubmitted(false);
}, [customerId]);
// After: delete the effect; in the parent
<OrderPanel key={customerId} customerId={customerId} items={items} history={history} />
A regra no-adjust-state-on-prop-change tem como alvo a versão parcial desse cenário, em que apenas parte do estado muda em resposta a uma prop, embora ela também reporte o Effect 4, como mostra a execução do lint acima. Quando só parte do estado deve ser resetada, a correção geralmente é reestruturar o estado ou calcular o valor durante a renderização.
Falsos positivos e effects que devem permanecer
Effects que conectam um componente a algo que o React não gerencia (timers, APIs do DOM, widgets que não são React, conexões de rede) são exatamente a tarefa para a qual o useEffect foi criado, então mantenha-os. A maioria deles precisa de uma função de cleanup, como explica Synchronizing with Effects. Se uma medição do DOM causar um flicker visível, a escolha entre useEffect e useLayoutEffect importa mais do que a decisão de manter ou não o effect.
Assinaturas de uma store externa são a exceção à regra de manter effects de sincronização. Elas pertencem ao useSyncExternalStore, que lê um snapshot durante a renderização em vez de copiá-lo para o estado a partir de um effect. A regra no-external-store-subscription sinaliza a versão baseada em effect.
A análise estática de effects tem casos-limite. A própria regra set-state-in-effect do React avisa quando um effect chama um setter de estado de forma síncrona, e uma issue do React sobre o rigor da set-state-in-effect questiona o quão rigorosamente ela trata flags de montagem e padrões de detecção do lado do cliente. Trate avisos nesses formatos como um convite para revisar o código, e não como um veredito em qualquer direção. Executar os dois plugins é uma escolha razoável: a regra do React cobre setState em effects, e este plugin acrescenta callbacks para o pai, atualizações encadeadas e assinaturas de stores.
Antes de suprimir um aviso, pergunte:
- O effect se comunica com um sistema externo ao React?
- Ele precisa de cleanup quando as dependências mudam ou o componente é desmontado?
- Movê-lo para a renderização ou para um handler alteraria um comportamento visível para os usuários?
Se as respostas justificarem mantê-lo, desabilite a regra naquela linha e use a sintaxe de descrição -- do ESLint para registrar o motivo:
// eslint-disable-next-line react-you-might-not-need-an-effect/no-initialize-state -- client-only value, avoids hydration mismatch
Esse prefixo corresponde ao usado pelos presets, pelo Oxlint e pelas configurações legadas. Use reactYouMightNotNeedAnEffect/ apenas se você mesmo registrou o plugin com essa chave.
Implantação
Implante o eslint-plugin-react-you-might-not-need-an-effect primeiro com o recommended, para que os apontamentos apareçam como avisos e o CI continue passando. Trate-os um componente por vez, refatorando ou anotando cada um, e mude para o strict quando a contagem de avisos chegar a zero, para que novos effects desnecessários quebrem o build.
Perguntas frequentes
Posso usar o eslint-plugin-react-you-might-not-need-an-effect com uma configuração .eslintrc legada?
Sim. Projetos com .eslintrc legado estendem plugin:react-you-might-not-need-an-effect/legacy-recommended para reportar todas as regras como avisos, ou plugin:react-you-might-not-need-an-effect/legacy-strict para reportá-las como erros. Em configurações legadas, os IDs das regras e os comentários de desativação usam o prefixo em kebab-case react-you-might-not-need-an-effect/, o mesmo prefixo usado pelos presets de flat config.
Quando devo envolver um valor derivado em useMemo em vez de calculá-lo a cada renderização?
Use useMemo apenas quando o cálculo for comprovadamente lento. A documentação do React sugere medir o tempo com console.time e memoizar se o total chegar a cerca de 1ms ou mais. O useMemo armazena o resultado em cache entre renderizações sem colocá-lo no estado, então ele continua correto já na primeira renderização. O React Compiler consegue memoizar muitos cálculos automaticamente, o que torna chamadas manuais de useMemo para valores derivados necessárias com menos frequência.
Alterar a key de um componente também reseta o estado dos filhos?
Sim. Quando a key muda, o React desmonta o componente e toda a sua subárvore e, em seguida, monta novas instâncias. Todos os filhos perdem seu estado local, os nós do DOM são recriados e todos os effects executam seu cleanup e depois rodam novamente. Se apenas parte do estado deve ser resetada enquanto o estado dos filhos é preservado, ajuste esse estado diretamente ou reestruture-o em vez de usar key.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k