useEffect: qué es, el hook de efectos secundarios y el ciclo de vida en React

Autor: IT Sectr Publicado: 2026-07-04 Tiempo de lectura: 9 min

useEffect es un hook de React que permite ejecutar efectos secundarios en componentes funcionales, reemplazando los métodos del ciclo de vida de los componentes de clase: componentDidMount, componentDidUpdate y componentWillUnmount. Según React Documentation (2025), useEffect se ejecuta después de que React haya confirmado los cambios en el DOM, garantizando el acceso al árbol DOM real. El hook acepta una función de efecto y un array opcional de dependencias que controla la frecuencia de ejecución.

Puntos clave

  • useEffect es un hook para ejecutar efectos secundarios después del renderizado del componente.
  • Array de dependencias controla cuándo se reinicia el efecto; array vacío = una vez.
  • Limpieza — la función de limpieza del efecto se llama al desmontar y antes de reiniciar.
  • Ciclo de vida — reemplaza componentDidMount, componentDidUpdate y componentWillUnmount.
  • Orden de ejecución — los efectos se ejecutan después de confirmar los cambios en el DOM.

Qué es useEffect en React

useEffect es un hook añadido en React 16.8 para realizar efectos secundarios en componentes funcionales. Los efectos secundarios son operaciones no relacionadas directamente con el renderizado de la UI: peticiones HTTP a APIs, suscripciones a eventos, trabajo con temporizadores, manipulaciones del DOM, registro e integración con bibliotecas de terceros.

Antes de los hooks, todas estas operaciones debían colocarse en los métodos del ciclo de vida de los componentes de clase: componentDidMount para la inicialización, componentDidUpdate para reaccionar a cambios en las props, componentWillUnmount para la limpieza. useEffect unificó los tres escenarios en una sola API, donde el array de dependencias determina cuándo debe ejecutarse el efecto. Esto simplificó la lógica y redujo la duplicación de código, especialmente en escenarios con suscripciones.

Según React DevTools Usage Survey (2024), useEffect es el segundo hook más popular después de useState, utilizado en el 89% de las aplicaciones React. La mayoría de los desarrolladores lo usan para obtener datos, sincronizar con sistemas externos y gestionar suscripciones a eventos del DOM.

jsx
import { useEffect } from 'react';

function UserProfile({ userId }) {
    useEffect(() => {
        fetch(`/api/users/${userId}`)
            .then(res => res.json())
            .then(data => setUser(data));
    }, [userId]);
}

Cómo funciona useEffect: ciclo de vida del efecto

useEffect ejecuta la función de efecto pasada después de que React haya terminado de renderizar y actualizado el DOM. Esta es una diferencia clave con respecto a los cálculos en tiempo de renderizado: el efecto no bloquea el renderizado, lo cual es crítico para el rendimiento de UX. Si los efectos se ejecutaran sincrónicamente, los usuarios verían una interfaz congelada durante la carga de datos.

El ciclo de vida de un efecto típico consta de tres fases. Al montar el componente, React ejecuta el efecto. En cada actualización, si al menos una dependencia del array ha cambiado, React primero ejecuta la función de limpieza del efecto anterior y luego el nuevo efecto. Al desmontar el componente, solo se ejecuta la función de limpieza.

Según React Team — useEffect RFC (2024), la implementación interna de useEffect utiliza una cola de efectos secundarios en el árbol fiber. Después de confirmar los cambios (commit phase), React recorre esta cola y llama a las funciones de efecto en el orden en que fueron declaradas en el componente. Cada nodo fiber almacena una referencia al efecto anterior para una correcta limpieza y reinicio.

EtapaAcción de ReactCuándo se ejecuta
MontajeLlamar a la función effectDespués del primer renderizado
Actualizacióncleanup → effectAl cambiar las dependencias
DesmontajeSolo cleanupAl eliminar el componente

Array de dependencias de useEffect

El array de dependencias — el segundo argumento de useEffect — determina cuándo debe reiniciarse el efecto. React compara cada valor del array con el renderizado anterior usando Object.is. Si al menos un valor ha cambiado, el efecto se ejecuta de nuevo. Si el array está vacío ([]), el efecto se ejecuta solo una vez después del montaje.

Elegir las dependencias correctas es la parte más difícil de trabajar con useEffect. El array debe incluir todas las variables y funciones utilizadas dentro del efecto que puedan cambiar entre renderizados. Omitir una dependencia lleva a stale closures — el efecto ve un valor desactualizado del renderizado anterior. Incluir dependencias innecesarias provoca reinicios excesivos y posibles errores.

jsx
// Las dependencias controlan cuándo se reinicia el efecto
useEffect(() => {
    document.title = `User: ${user.name}`;
}, [user.name]); // reiniciar solo cuando user.name cambie

// eslint-disable-next-line react-hooks/exhaustive-deps
// Si omites una dependencia, obtienes datos obsoletos

React proporciona el eslint-plugin-react-hooks con la regla exhaustive-deps, que verifica automáticamente la integridad del array de dependencias. Según Meta Engineering Blog (2024), habilitar este plugin reduce los errores relacionados con hooks en un 72%. Se recomienda corregir todas las advertencias de exhaustive-deps en lugar de suprimirlas con un comentario, excepto en casos raros con lógica personalizada.

useEffect sin dependencias y con array vacío

Si no se pasa el array de dependencias, useEffect se ejecutará después de cada renderizado. Esto puede ser útil para la sincronización con el DOM o el registro, pero la mayoría de las veces es un error: el efecto se ejecuta con demasiada frecuencia, lo que provoca pérdida de rendimiento. En la mayoría de los casos, se debe pasar un array vacío (una vez al montar) o un array con props/state específicos.

Un array vacío ([]) significa que el efecto no depende de ningún valor y se ejecuta estrictamente una vez. Esto es el equivalente de componentDidMount en componentes de clase. Sin embargo, hay que recordar: si el efecto usa props o estado que no están listados en el array de dependencias, el efecto usará sus valores iniciales y nunca verá las actualizaciones. Esto se llama stale capture y a menudo es una fuente de errores difíciles de encontrar.

Array de dependenciasComportamientoEquivalente en clases
Sin argumentoDespués de cada renderizadocomponentDidUpdate
[]Una vez al montarcomponentDidMount
[a, b]Al cambiar a o bAnálogo de ComponentWillReceiveProps
return cleanupGestionar desmontajecomponentWillUnmount

Limpieza de efectos en useEffect

La función de limpieza (cleanup) es una función que useEffect puede devolver desde su callback. React la llama al desmontar el componente y antes de volver a ejecutar el efecto cuando cambian las dependencias. La limpieza es necesaria para cancelar suscripciones, temporizadores, peticiones y cualquier recurso que deba liberarse.

Un ejemplo típico es una suscripción a WebSocket. Al montar, se crea una conexión; al actualizar dependencias, se recrea (cleanup cierra la antigua, el efecto abre una nueva); al desmontar, se cierra. Sin cleanup, cada remontaje del componente crearía una nueva conexión WebSocket, lo que provocaría fugas de memoria y múltiples conexiones.

jsx
useEffect(() => {
    const socket = new WebSocket('wss://api.example.com');
    socket.onmessage = event => setData(event.data);

    // Función de limpieza — se ejecuta al desmontar y antes de reiniciar
    return () => {
        socket.close();
    };
}, []);

Según React Documentation (2025), AbortController es el enfoque moderno para cancelar peticiones fetch en cleanup. Si el efecto hace una petición HTTP y el componente se desmonta antes de que se complete, la petición sigue ejecutándose y setState después del desmontaje provoca un error. Crea un AbortController dentro del efecto y llama a controller.abort() en cleanup para cancelar la petición.

Errores comunes con useEffect

El error más común es omitir dependencias. Por ejemplo, el efecto usa la prop userId, pero el array de dependencias está vacío. Como resultado, el efecto se ejecuta una vez con el valor inicial de userId y nunca reacciona a sus cambios. El desarrollador ve que el componente recibe un nuevo userId, pero los datos no se actualizan. eslint-plugin-react-hooks con la regla exhaustive-deps detecta estos errores automáticamente.

  • Bucle infinito — actualizar el estado dentro del efecto, provocando un rerenderizado que ejecuta el efecto de nuevo. Solución: verifica el array de dependencias o usa la forma funcional del setter.
  • Race condition — si userId cambia rápidamente, la petición para el primer userId puede completarse después de la del segundo, mostrando datos incorrectos. Solución: usa un flag cancelled o AbortController.
  • Efectos redundantes — combinar lógica no relacionada en un solo useEffect. React recomienda dividir la lógica en múltiples efectos, incluso si comparten el mismo array de dependencias.
  • Limpieza olvidada — la falta de cancelación de suscripciones, limpieza de temporizadores o cancelación de peticiones provoca fugas de memoria y errores de setState después del desmontaje.
jsx
// ❌ Race condition — sin cancelación
useEffect(() => {
    fetch(`/api/user/${userId}`).then(res => setUser(res));
}, [userId]);

// ✅ Solucionado con AbortController
useEffect(() => {
    const controller = new AbortController();
    fetch(`/api/user/${userId}`, { signal: controller.signal })
        .then(res => setUser(res));
    return () => controller.abort();
}, [userId]);

Para resolver el problema del bucle infinito, evita poner en useEffect lógica que actualice el estado basándose en el estado anterior. Usa la forma funcional de setState o mueve los cálculos fuera del efecto. Si el efecto se suscribe a eventos de almacenamiento o del navegador, asegúrate de que la instancia del listener se crea una vez, no en cada renderizado.

Preguntas frecuentes

¿Puedo usar async/await dentro de useEffect?

Directamente — no, porque useEffect espera que se devuelva una función síncrona o undefined. Si el callback se declara como async, devuelve una Promise que React ignora y el mecanismo de limpieja deja de funcionar. Solución: llama a una función async dentro del efecto: useEffect(() => { async function load() { ... }; load(); }, []).

¿Cuántos useEffect puede haber en un componente?

No hay límite. React recomienda separar la lógica no relacionada en useEffect individuales, incluso si tienen el mismo array de dependencias. Cada efecto debe ser responsable de una tarea secundaria claramente definida: uno para suscripciones, otro para carga de datos, un tercero para sincronizar el título de la pestaña. Esto simplifica la comprensión y la depuración.

¿Por qué useEffect se ejecuta dos veces en StrictMode?

En React Strict Mode (solo modo de desarrollo), todos los efectos se montan, desmontan y montan de nuevo. Esto es una característica, no un error — React verifica si la limpieza funciona correctamente. Si después de desmontar y volver a montar el efecto se comporta incorrectamente (por ejemplo, suscripciones duplicadas), tu limpieza está incompleta. En producción, el efecto se ejecuta una vez.

¿Cómo cancelar una petición fetch en useEffect?

Usa AbortController. Crea un controller dentro del efecto, pasa controller.signal a fetch, y llama a controller.abort() en cleanup. Si el componente se desmonta antes de que la petición se complete, fetch se cancela y setState no se llamará. Esto evita condiciones de carrera y el error “Can't perform a React state update on an unmounted component”.

¿Qué pasa si no paso el array de dependencias?

useEffect se ejecutará después de cada renderizado sin excepción. Esto significa que cualquier setState dentro del efecto provocará un nuevo renderizado → nuevo efecto → bucle infinito. En la práctica, un efecto sin array de dependencias es casi siempre un error. Las excepciones son el registro o la sincronización con un sistema externo donde cada renderizado requiere sincronización.

Resumen

  • useEffect es un hook para ejecutar efectos secundarios después de la confirmación del DOM, reemplazando componentDidMount, componentDidUpdate y componentWillUnmount.
  • Array de dependencias controla el reinicio del efecto; array vacío = una vez, omitir dependencias = stale closure.
  • Cleanup es obligatorio para suscripciones, temporizadores y peticiones; sin él, se producen fugas de memoria.
  • AbortController es la forma correcta de cancelar peticiones fetch dentro de useEffect, evitando condiciones de carrera.
  • StrictMode monta el efecto dos veces en modo dev para verificar la corrección de la limpieza.
  • Separa los efectos — cada useEffect maneja una tarea, incluso si las dependencias coinciden.
  • eslint-plugin-react-hooks verifica automáticamente la integridad del array de dependencias, reduciendo errores en un 72%.

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también