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 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.
import { useEffect } from 'react';
function UserProfile({ userId }) {
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => setUser(data));
}, [userId]);
}
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.
| Etapa | Acción de React | Cuándo se ejecuta |
|---|---|---|
| Montaje | Llamar a la función effect | Después del primer renderizado |
| Actualización | cleanup → effect | Al cambiar las dependencias |
| Desmontaje | Solo cleanup | Al eliminar el componente |
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.
// 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.
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 dependencias | Comportamiento | Equivalente en clases |
|---|---|---|
| Sin argumento | Después de cada renderizado | componentDidUpdate |
| [] | Una vez al montar | componentDidMount |
| [a, b] | Al cambiar a o b | Análogo de ComponentWillReceiveProps |
| return cleanup | Gestionar desmontaje | componentWillUnmount |
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.
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.
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.
// ❌ 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
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(); }, []).
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.
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.
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”.
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
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.
Lea también