miércoles, 4 de marzo de 2026

Dependencias y no dependencias de useEffect

¿Es cierto que si un efecto actualiza un estado es obligarotio que lo tenga como dependencia?

No, no es obligatorio incluir como dependencia de useEffect el estado que se actualiza dentro del efecto. Lo que sí es obligatorio es incluir como dependencia todo valor que se lee dentro del efecto y que puede cambiar entre renders.

📌 Diferencia clave

Caso¿Debe estar en dependencias?Motivo
setState(valorFijo)❌ NoNo depende de nada cambiante
setState(prev => ...)❌ NoReact maneja el valor internamente
setState(valorDerivadoDeX)✅ Sí (x debe estar)Porque x puede cambiar
if (condiciónBasadaEnY) setState(...)✅ Sí (y debe estar)Porque la lógica depende de y

📌 Ejemplo correcto

tsx
useEffect(() => {
  setCount(prev => prev + 1); // ✅ No necesita `count` como dependencia
}, []);

📌 Ejemplo incorrecto

tsx
useEffect(() => {
  setCount(count + 1); // ❌ Si usás `count`, debe estar en dependencias
}, []);

Este segundo ejemplo puede usar un valor obsoleto de count si no lo incluís en el array de dependencias.

✅ Conclusión

  • No es obligatorio incluir el estado que se actualiza, salvo que lo estés leyendo dentro del efecto.

  • Si usás prev => ..., estás protegido contra valores stale y no necesitás incluir el estado como dependencia.

  • Si usás el valor directamente (count + 1), tenés que incluirlo.

¿Querés que te arme una guía visual de dependencias en useEffect, con ejemplos de cuándo incluir y cuándo no incluir cada variable? 


Pero ¿Cómo pordría un valor desactualizado llegar a un efecto siendo que el efecto es siempre lo último en ejecutarse en el ciclo de render?

Excelente observación, Gabriel. Es cierto que los efectos (useEffect) se ejecutan después del render, pero eso no significa que siempre tengan acceso al estado más actualizado si ese estado fue capturado antes del render actual. Vamos a descomponerlo:

📌 ¿Por qué puede llegar un valor desactualizado al efecto?

1. Captura de valores previos

Si el efecto depende de una variable (count, por ejemplo), y esa variable fue capturada en el cierre (closure) de una función anterior, entonces el efecto puede estar usando una versión anterior del estado, aunque se ejecute después del render.

tsx
useEffect(() => {
  const interval = setInterval(() => {
    console.log(count); // ❌ puede estar desactualado
  }, 1000);
  return () => clearInterval(interval);
}, []);
  • Aunque el efecto se ejecuta después del render, el count que se usa dentro del setInterval es el que estaba en el cierre cuando el efecto se definió.

  • Como el array de dependencias está vacío ([]), el efecto no se vuelve a ejecutar cuando count cambia.

2. Efectos con dependencias vacías

Cuando no incluís una variable en el array de dependencias, el efecto no se actualiza con los nuevos valores de esa variable. Esto es lo que genera el “valor stale” (obsoleto).

3. Solución con updater function

Si en vez de leer el valor directamente usás una función:

tsx
setCount(prev => prev + 1);
  • No necesitás depender de count en el efecto.

  • React garantiza que el prev es el valor más reciente, incluso si el efecto no se volvió a ejecutar.

✅ Conclusión

Aunque los efectos se ejecutan después del render, pueden usar valores desactualizados si esos valores fueron capturados antes del render actual y no están en las dependencias. Por eso, el updater function (prev => ...) es tan valioso: evita depender de valores que podrían estar stale.

¿Querés que te arme un ejemplo con setInterval donde se ve claramente cómo el valor se queda obsoleto si no se usa prev => ..., y cómo se soluciona?


SÍ.

Perfecto, te muestro un ejemplo práctico con setInterval para que veas cómo un valor puede quedar obsoleto si no usás la versión con prev => ..., y cómo se soluciona:

❌ Ejemplo con valor stale

tsx
import { useState, useEffect } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      // Usa "count" directamente
      setCount(count + 1);
      console.log("Dentro del intervalo:", count);
    }, 1000);

    return () => clearInterval(interval);
  }, []); // Dependencias vacías

  return <div>{count}</div>;
}

Qué pasa aquí:

  • El efecto se ejecuta una sola vez (porque [] está vacío).

  • El count capturado en el cierre es siempre 0.

  • Entonces, cada tick del intervalo hace setCount(0 + 1) → el estado nunca pasa de 1.

  • El console.log muestra siempre el valor viejo.

✅ Ejemplo correcto con updater function

tsx
import { useState, useEffect } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      // Usa la función con "prev"
      setCount(prev => prev + 1);
      console.log("Dentro del intervalo:", count);
    }, 1000);

    return () => clearInterval(interval);
  }, []); // Dependencias vacías

  return <div>{count}</div>;
}

Qué pasa aquí:

  • El efecto se ejecuta una sola vez.

  • Cada tick del intervalo usa el valor más reciente de count gracias a prev.

  • El estado se incrementa correctamente: 0 → 1 → 2 → 3 → …

  • El componente se re-renderiza con el valor actualizado.

✅ Conclusión

El efecto se ejecuta al final del ciclo de render, pero si no incluís la variable en dependencias, el cierre captura un valor viejo. La forma segura de evitarlo es usar la versión con updater function (prev => ...), que siempre trabaja con el estado más reciente.

¿Querés que te arme un diagrama visual que muestre cómo el cierre captura el valor viejo en el primer ejemplo y cómo el updater function evita ese problema?