Se você já trabalhou com React em produção, sabe que existe um tipo de bug especialmente traiçoeiro: aquele que simplesmente não aparece no ambiente local. Tudo funciona perfeitamente no desenvolvimento, os testes passam, o deploy vai tranquilo… e, de repente, começam a surgir comportamentos estranhos que ninguém consegue reproduzir com facilidade.
Com o React moderno — especialmente após a introdução de concorrência, Suspense e novas formas de renderização — esses problemas ficaram mais comuns. Isso acontece porque o modelo mental mudou, mas o jeito de programar de muita gente continua o mesmo.
Neste artigo, vamos explorar 7 erros reais que surgem apenas em produção e, mais importante, como evitá-los antes que virem dor de cabeça.
1. Efeitos duplicados e chamadas duplicadas de API
Esse é um clássico que confunde muita gente. Em desenvolvimento, especialmente com Strict Mode ativado, o React executa certos efeitos duas vezes para detectar problemas. Em produção, isso não acontece — o que pode mascarar bugs ou, pior, fazer você “corrigir” algo que não está quebrado.
O problema aparece quando o código não é idempotente e acaba gerando chamadas duplicadas ou estados inconsistentes.
Exemplo problemático
function Users() {
useEffect(() => {
fetch('/api/users');
}, []);
return <div>...</div>;
}
Dependendo da lógica envolvida (especialmente com side effects externos), você pode ter duplicidade ou inconsistência.
Versão mais segura
function Users() {
useEffect(() => {
let isMounted = true;
async function load() {
const res = await fetch('/api/users');
if (isMounted) {
// atualizar estado com segurança
}
}
load();
return () => {
isMounted = false;
};
}, []);
return <div>...</div>;
}
Aqui você protege o ciclo de vida e evita efeitos colaterais inesperados.
2. Race conditions em requisições concorrentes
Com concorrência, o React pode disparar múltiplas atualizações, e isso abre espaço para um problema clássico: respostas chegando fora de ordem.
Exemplo real
function Search() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
useEffect(() => {
fetch(`/api/search?q=${query}`)
.then(res => res.json())
.then(setResults);
}, [query]);
return <input onChange={e => setQuery(e.target.value)} />;
}
Se o usuário digitar rápido:
- requisição A (lenta)
- requisição B (rápida)
👉 A pode sobrescrever B
Solução com cancelamento
function Search() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
useEffect(() => {
const controller = new AbortController();
async function fetchData() {
const res = await fetch(`/api/search?q=${query}`, {
signal: controller.signal,
});
const data = await res.json();
setResults(data);
}
fetchData();
return () => controller.abort();
}, [query]);
return <input onChange={e => setQuery(e.target.value)} />;
}
Agora você evita sobrescrita indevida.
3. Uso incorreto de Suspense causando telas travadas
Suspense é poderoso, mas mal utilizado pode travar a interface inteira, especialmente quando você envolve blocos grandes demais sem granularidade.
Exemplo problemático
<Suspense fallback={<p>Carregando...</p>}>
<Dashboard />
</Suspense>
Se o Dashboard for pesado:
👉 a tela inteira fica bloqueada
Abordagem mais granular
<Suspense fallback={<p>Carregando perfil...</p>}>
<UserProfile />
</Suspense>
<Suspense fallback={<p>Carregando posts...</p>}>
<UserPosts />
</Suspense>
Agora o carregamento é progressivo e mais fluido.
4. Hidratação inconsistente em SSR
Se você usa Next.js ou qualquer SSR, esse erro é comum.
O HTML gerado no servidor precisa bater com o que o cliente renderiza.
Se não bater:
👉 erro de hidratação
👉 comportamento imprevisível
Exemplo problemático
export default function Page() {
const random = Math.random();
return <p>{random}</p>;
}
Servidor e cliente vão gerar valores diferentes.
Solução
export default function Page() {
const [random, setRandom] = useState<number | null>(null);
useEffect(() => {
setRandom(Math.random());
}, []);
return <p>{random}</p>;
}
Agora o valor só existe no client.
5. Re-renderizações em cascata (performance invisível)
Esse tipo de problema não quebra funcionalidade, mas destrói performance em produção.
Exemplo
function Parent() {
const [count, setCount] = useState(0);
return <Child onClick={() => setCount(count + 1)} />;
}
Cada render cria uma nova função.
👉 Child re-renderiza sempre
Correção
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);
return <Child onClick={handleClick} />;
6. Estado desatualizado (stale closures)
Esse bug é clássico e difícil de perceber.
Exemplo problemático
function Counter() {
const [count, setCount] = useState(0);
function incrementLater() {
setTimeout(() => {
setCount(count + 1);
}, 1000);
}
return <button onClick={incrementLater}>{count}</button>;
}
Se clicar várias vezes:
👉 valor pode ficar inconsistente
Correção
setCount(c => c + 1);
Agora sempre usa o estado mais recente.
7. Dependências erradas no useEffect
Esse é silencioso e perigoso.
Exemplo
useEffect(() => {
fetchData(userId);
}, []);
Se userId mudar:
👉 efeito não roda novamente
Correção
useEffect(() => {
fetchData(userId);
}, [userId]);
O padrão invisível por trás de todos esses erros
Se você observar bem, todos esses problemas têm algo em comum:
👉 assumem comportamento síncrono
👉 ignoram concorrência
👉 ignoram ciclo real de render
O modelo mental correto
React moderno não garante:
- ordem de execução
- número de renders
- sincronização simples
Ele garante:
👉 consistência final da UI
Conclusão: produção expõe o que o dev esconde
Ambiente local é controlado. Produção não é.
É em produção que aparecem latência real, concorrência real e uso real. E é aí que decisões aparentemente pequenas viram problemas grandes.




