O bug familiar que irrita todo desenvolvedor React
\nVocê já viu no console do navegador o aviso Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application.? Se sim — você não está sozinho. Esta pergunta no Stack Overflow acumulou milhares de visualizações e se tornou uma verdadeira dor de cabeça para iniciantes e desenvolvedores experientes em React.
Neste artigo, vamos entender por que esse erro ocorre, como corrigi-lo usando TypeScript e, principalmente, como otimizar o desempenho da sua aplicação para que esses avisos não apareçam mais. Vamos lá!
\n\nPor que o React reclama sobre um componente desmontado?
\nImagine: o usuário abriu uma página, iniciou o carregamento de dados (por exemplo, via fetch ou axios), mas não esperou a resposta e navegou para outra tela. O componente que iniciou a requisição já foi desmontado (removido do DOM), mas a operação assíncrona ainda está em execução. Quando a resposta chega, o código tenta atualizar o estado (state) de um componente que já não existe. O React reclama com razão: "Ei, não posso atualizar algo que não existe!".
Em TypeScript, esse problema é especialmente perceptível se você usa tipagem estrita e esquece as funções de cleanup no useEffect.
Exemplo clássico do problema (TypeScript)
\nimport React, { useState, useEffect } from 'react';\n\ninterface User {\n id: number;\n name: string;\n}\n\nconst UserProfile: React.FC<{ userId: number }> = ({ userId }) => {\n const [user, setUser] = useState<User | null>(null);\n\n useEffect(() => {\n fetch(`/api/users/${userId}`)\n .then(res => res.json())\n .then(data => setUser(data)); // Erro! O componente pode estar desmontado\n }, [userId]);\n\n return <div>{user?.name}</div>;\n};\nSe o usuário mudar rapidamente de rota, o componente UserProfile será removido e o setUser será chamado em vão. Obtemos um vazamento de memória e um erro no console.
3 formas de corrigir o erro (com exemplos em TypeScript)
\nVamos analisar três abordagens confiáveis que resolvem o problema e melhoram o desempenho da aplicação React.
\n\nForma 1: Flag usando useRef (a mais popular)
\nCriamos uma flag ref que indica se o componente está montado. Atualizamos o estado apenas se a flag for true.
import React, { useState, useEffect, useRef } from 'react';\n\nconst UserProfile: React.FC<{ userId: number }> = ({ userId }) => {\n const [user, setUser] = useState<User | null>(null);\n const isMounted = useRef(true);\n\n useEffect(() => {\n // Redefinimos a flag a cada montagem\n isMounted.current = true;\n\n fetch(`/api/users/${userId}`)\n .then(res => res.json())\n .then(data => {\n if (isMounted.current) {\n setUser(data);\n }\n });\n\n // Cleanup: define a flag como false ao desmontar\n return () => {\n isMounted.current = false;\n };\n }, [userId]);\n\n return <div>{user?.name}</div>;\n};\nVantagens: simplicidade, não requer bibliotecas adicionais. Desvantagem: é preciso lembrar de fazer o cleanup.
\n\nForma 2: AbortController para cancelar a requisição fetch (recomendada)
\nUma abordagem moderna e correta é cancelar a própria requisição, em vez de apenas ignorar a resposta. Isso economiza recursos do servidor e do cliente.
\nimport React, { useState, useEffect } from 'react';\n\nconst UserProfile: React.FC<{ userId: number }> = ({ userId }) => {\n const [user, setUser] = useState<User | null>(null);\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n const controller = new AbortController();\n const signal = con