Menu

Hook useEffect en React: dependencias, limpieza y ejemplos

useEffect ejecuta código después de que React actualizó la pantalla, para que un componente se sincronice con algo fuera de React: un temporizador, una suscripción, un event listener. Aprende las tres formas del array de dependencias, la limpieza, los bucles infinitos y cuándo no necesitas un efecto.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

useEffect ejecuta un trozo de código después de que React renderizó tu componente y actualizó la pantalla. Lo usas para mantener un componente sincronizado con algo fuera de React, como un temporizador, una conexión de red o un event listener, y controlas cuándo vuelve a ejecutarse con un array de dependencias.

Abre la consola bajo la vista previa: el efecto registra una vez cuando aparece el componente, y otra vez después de cada clic. Cambia [count] por [] y vuelve a hacer clic: el efecto se queda en silencio después del primer registro.

La sintaxis

useEffect(() => {
    // setup: runs after the render is on the screen
    return () => {
        // cleanup: runs before the next setup, and on unmount
    };
}, [dependencies]);
  • El primer argumento es la función de configuración (setup). Puede devolver una función de limpieza, o nada.
  • El segundo argumento es el array de dependencias: cada valor del componente que lee la configuración (props, estado y todo lo que se calcule a partir de ellos).
  • Llama a useEffect en el nivel superior del componente, como cualquier hook.

Los efectos se ejecutan después del renderizado

Renderizar es React llamando a tu función de componente para saber qué debe mostrar la pantalla. Los efectos esperan a que ese trabajo termine y el DOM esté actualizado, así que dentro de un efecto la página ya muestra los valores nuevos.

Cada clic registra primero el renderizado y después el efecto, y el efecto lee el texto del botón que React acaba de escribir. Por eso leer el DOM, iniciar un temporizador o conectarse a un servidor va en un efecto y no en el cuerpo del componente: el cuerpo se ejecuta durante el renderizado, antes de que haya algo en pantalla, y React puede llamarlo más a menudo de lo que esperas.

Los efectos normalmente se ejecutan después de que el navegador pintó. Cuando un efecto tiene que medir el DOM antes del pintado, usa useLayoutEffect, que siempre se ejecuta antes del pintado.

El array de dependencias: tres formas

El array de dependencias decide cuándo vuelve a ejecutarse el efecto. Hay tres formas, y este bloque usa las tres para que veas la diferencia.

Al cargar, los tres efectos registran. Haz clic en a + 1 y se ejecutan dos: el que no tiene array y el que depende de a. Haz clic en b + 1 y solo se ejecuta el que no tiene array, porque nada en [] ni en [a] cambió.

Array de dependenciasCuándo se ejecuta el efecto
ningunoDespués de cada renderizado
[]Una vez, después del primer renderizado (al montar)
[a, b]Después del primer renderizado, y luego cada vez que a o b cambiaron

React compara cada dependencia con su valor anterior usando Object.is. Los números, cadenas y booleanos se comparan por valor. Los objetos, arrays y funciones se comparan por identidad, lo que importa mucho (ver más abajo).

No dejes un valor fuera del array para que un efecto se ejecute menos. El efecto seguiría usando el valor de un renderizado viejo. La regla de lint react-hooks/exhaustive-deps lo detecta; forma parte de la configuración de ESLint que incluyen la plantilla de React de Vite y la configuración de ESLint de Next.js.

Limpieza

Muchos efectos inician algo que hay que detener: una conexión, una suscripción, un temporizador. Devuelve una función desde el efecto y React la llama en dos momentos: justo antes de que el efecto vuelva a ejecutarse con nuevas dependencias, y cuando el componente sale de la pantalla (desmontaje).

Elige otra sala: la consola muestra disconnect from general y luego connect to music. La conexión vieja siempre se cierra antes de que se abra la nueva. Haz clic en Hide y la limpieza se ejecuta una última vez, porque ChatRoom se desmontó.

La función connect de aquí sustituye a un cliente real de chat o WebSocket. La forma es la misma para cualquiera de ellos: créalo en la configuración, ciérralo en la limpieza.

Limpiar un temporizador

Un temporizador iniciado en un efecto sigue funcionando después de que el componente desaparece, a menos que la limpieza lo detenga.

Borra la línea clearInterval(id); y luego oculta el temporizador: tick sigue llegando a la consola desde un componente que ya no existe. Cada Show inicia otro más.

Fíjate en setSeconds((s) => s + 1). El efecto se ejecuta una vez, así que un setSeconds(seconds + 1) simple leería para siempre el seconds del primer renderizado y se quedaría en 1. La función de actualización siempre recibe el valor más reciente.

Quitar un event listener

Los event listeners en window o document siguen el mismo patrón. Pasa a removeEventListener la misma función que pasaste a addEventListener:

useEffect(() => {
    function handleKeyDown(e) {
        if (e.key === 'Escape') onClose();
    }
    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
}, [onClose]);

El bucle infinito

Un efecto que cambia un valor de estado del que también depende inicia un bucle: el estado cambia, React renderiza, la dependencia cambió, el efecto se ejecuta y vuelve a cambiar el estado.

// Never stops: every render runs the effect, every effect causes a render
useEffect(() => {
    setCount(count + 1);
});

// Same loop: count changes, so the effect runs again
useEffect(() => {
    setCount(count + 1);
}, [count]);

El componente renderiza sin fin y la pestaña se queda ocupada. React no detiene este bucle: en desarrollo registra un error "Maximum update depth exceeded" en la consola, y en producción no registra nada. Este bloque ejecuta la segunda versión con una condición de parada, para que puedas ver el bucle sin congelar la página:

La consola registra de render 0 a render 5: seis renderizados para un solo montaje, cada uno iniciado por el efecto. Las soluciones dependen de lo que querías:

  • Lo querías una sola vez. Usa [] y la forma de actualización, setCount((c) => c + 1), para que count no sea una dependencia.
  • Estás calculando un valor a partir de otro estado. No lo guardes; calcúlalo durante el renderizado, como se muestra en la última sección de esta página.
  • Estás reaccionando a una acción del usuario. Pon el código en el manejador de eventos.

Objetos y funciones como dependencias

Un objeto, array o función creado en el cuerpo del componente es un valor nuevo en cada renderizado, así que un efecto que depende de él se ejecuta después de cada renderizado, aunque nada dentro haya cambiado.

Cambiar los colores no tiene nada que ver con la búsqueda, y sin embargo cada cambio vuelve a registrar search for react. Mueve la línea options dentro del efecto y cambia el array a [query]: los cambios de color quedan en silencio y solo escribir en el input ejecuta la búsqueda.

Esa es la solución general. Crea los objetos y funciones dentro del efecto que los usa, y depende de los valores primitivos con los que se construyen. Cuando una función tiene que vivir fuera del efecto, useCallback mantiene estable su identidad entre renderizados.

Por qué los efectos se ejecutan dos veces en desarrollo

Con <StrictMode> activado (una app nueva de Vite envuelve la raíz en él, y el App Router de Next.js lo activa por defecto), React hace una prueba en desarrollo: monta cada componente, ejecuta sus efectos, ejecuta sus limpiezas y vuelve a ejecutar los efectos. Para la sala de chat de arriba, la consola de desarrollo muestra:

connect to general
disconnect from general
connect to general

Esto ocurre una vez, al montar, y solo en desarrollo. Existe para exponer los efectos a los que les falta una limpieza: si dos ejecuciones seguidas rompen algo, el usuario también lo rompería al salir y volver. La solución nunca es bloquear la segunda ejecución con una ref; es escribir la limpieza. La vista previa de esta página funciona como un build de producción, así que aquí no verás la doble ejecución.

Puede que no necesites un efecto

Los efectos sirven para sincronizar con sistemas fuera de React. Dos usos comunes funcionan mejor sin uno.

Valores calculados a partir de props o estado. Calcúlalos durante el renderizado. Guardarlos en el estado y sincronizarlos con un efecto cuesta un renderizado extra, y el primero de esos renderizados pone un valor desactualizado en el DOM:

Cada clic renderiza WithEffect dos veces, y su primer renderizado todavía muestra el nombre viejo. Computed renderiza una vez con el valor correcto.

Código que responde a una acción del usuario. Enviar un formulario, mostrar un aviso después de una compra o enviar un evento de analítica por un clic va en el manejador de eventos. Ahí sabes exactamente qué pasó, mientras que un efecto solo sabe que algún valor cambió.

useEffectEvent

A veces un efecto necesita el valor más reciente de una prop sin volver a ejecutarse cuando esa prop cambia. En la sala de chat, una notificación debe usar el theme actual, pero cambiar el tema no debe reconectar. React 19.2 agrega useEffectEvent para esto:

import { useEffect, useEffectEvent } from 'react';

function ChatRoom({ roomId, theme }) {
    const onConnected = useEffectEvent(() => {
        showNotification('Connected!', theme); // always the latest theme
    });

    useEffect(() => {
        const connection = createConnection(roomId);
        connection.on('connected', () => onConnected());
        connection.connect();
        return () => connection.disconnect();
    }, [roomId]); // theme is not a dependency
}

Una función de useEffectEvent solo sirve para llamarla dentro de efectos. No la pases a otros componentes ni la incluyas en un array de dependencias.

Preguntas frecuentes

¿Cuándo se ejecuta useEffect?

Después de que React renderizó el componente y actualizó el DOM, normalmente después de que el navegador pintó. Sin array de dependencias se ejecuta después de cada renderizado, con [] solo después del primero, y con [a, b] después del primer renderizado y después de cualquier renderizado en el que a o b hayan cambiado.

¿Qué es la función de limpieza de useEffect?

La función que devuelves desde el efecto. React la llama antes de volver a ejecutar el efecto con nuevas dependencias, y una vez más cuando el componente se quita de la pantalla. Úsala para limpiar temporizadores, quitar event listeners y cerrar suscripciones.

¿Por qué mi useEffect se ejecuta dos veces?

En desarrollo, <StrictMode> monta cada componente, ejecuta sus efectos, los limpia y los vuelve a ejecutar, para mostrarte los efectos a los que les falta una limpieza. Ocurre una vez al montar y solo en desarrollo. Los builds de producción ejecutan el efecto una vez.

¿Por qué mi useEffect causa un bucle infinito?

El efecto cambia un valor de estado que también está en su array de dependencias (o no tiene array). Cambiar el estado vuelve a renderizar, la dependencia cambió, así que el efecto se ejecuta otra vez. Usa una función de actualización y quita la dependencia, agrega una condición, o calcula el valor durante el renderizado.

¿El callback de useEffect puede ser async?

No. El callback debe devolver nada o una función de limpieza, y una función async devuelve una promesa. Define una función async dentro del efecto y llámala, como se muestra en la página de obtener datos.

Ilustración de los lenguajes de programación de Coddy

Aprende a programar con Coddy

COMENZAR