No se puede actualizar el estado de React en un componente desmontado: cómo corregir y acelerar el código

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

El error familiar que enfurece a todo desarrollador de React

\n

¿Alguna vez has visto en la consola del navegador la advertencia 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.? Si es así, no estás solo. Esta pregunta en Stack Overflow ha acumulado miles de visitas y se ha convertido en un verdadero dolor de cabeza tanto para principiantes como para desarrolladores experimentados en React.

\n

En este artículo, analizaremos por qué ocurre este error, cómo solucionarlo con TypeScript y, lo más importante, cómo optimizar el rendimiento de tu aplicación para que estas advertencias no vuelvan a aparecer. ¡Allá vamos!

\n\n

¿Por qué React se queja de un componente desmontado?

\n

Imagina: un usuario abre una página, inicia la carga de datos (por ejemplo, mediante fetch o axios), pero no espera la respuesta y navega a otra pantalla. El componente que inició la solicitud ya está desmontado (eliminado del DOM), pero la operación asíncrona aún se está ejecutando. Cuando llega la respuesta, el código intenta actualizar el estado (state) de un componente que ya no existe. React se queja con razón: «¡Oye, no puedo actualizar algo que no existe!».

\n

En TypeScript, este problema es especialmente notable si usas tipado estricto y olvidas las funciones de limpieza (cleanup) en useEffect.

\n\n

Ejemplo clásico del problema (TypeScript)

\n
import 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)); // ¡Error! El componente puede estar desmontado\n  }, [userId]);\n\n  return <div>{user?.name}</div>;\n};
\n

Si el usuario cambia de ruta rápidamente, el componente UserProfile se eliminará y setUser se ejecutará sin un componente válido. Obtenemos una fuga de memoria y un error en la consola.

\n\n

3 formas de corregir el error (con ejemplos en TypeScript)

\n

Veamos tres enfoques confiables que resuelven el problema y mejoran el rendimiento de la aplicación React.

\n\n

Forma 1: Bandera con useRef (la más popular)

\n

Creamos una bandera ref que indica si el componente está montado. Actualizamos el estado solo si la bandera es true.

\n
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    // Reiniciamos la bandera en cada montaje\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: establecemos la bandera en false al desmontar\n    return () => {\n      isMounted.current = false;\n    };\n  }, [userId]);\n\n  return <div>{user?.name}</div>;\n};
\n

Ventajas: simplicidad, no requiere bibliotecas adicionales. Desventaja: hay que recordar hacer el cleanup.

\n\n

Forma 2: AbortController para cancelar la solicitud fetch (recomendada)

\n

El enfoque moderno y correcto es cancelar la solicitud en sí, no solo ignorar la respuesta. Esto ahorra recursos del servidor y del cliente.

\n
import 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                

Blogs

Book Recommendations