Mostrando entradas con la etiqueta useEffect. Mostrar todas las entradas
Mostrando entradas con la etiqueta useEffect. Mostrar todas las entradas

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?

jueves, 9 de octubre de 2025

Array de dependencias de useEffect de React

 ¿Es verdad que todas las variables de estado que se usan en la función de efecto (callback) de useEffect deben ser al mismo tiempo dependencias del mismo useEffect?

¡Esa es una de las reglas más importantes (y a menudo confusas) de React!

La respuesta corta es: Sí, es fundamentalmente cierto.

Todas las variables de estado, props y funciones declaradas dentro del componente que se utilizan dentro de la función de efecto (callback de useEffect) deben ser incluidas en el array de dependencias.

Esta es la forma en que React se asegura de que tu lógica se ejecute con los valores más recientes y correctos cada vez que sea necesario.


La Regla de la Exhaustividad de Dependencias

React sigue esta regla para prevenir errores sutiles y fugas de memoria causadas por cierres (closures) obsoletos.

¿Por qué es Necesario?

Cuando un componente se renderiza, la función useEffect captura los valores de las variables externas (state, props, etc.) que estaban presentes en ese renderizado específico.

  1. Renderizado 1: La variable contador es 0. El useEffect se ejecuta y captura contador = 0.

  2. Renderizado 2: El usuario hace clic, contador ahora es 1. El componente se re-renderiza.

  3. El Problema: Si olvidaste incluir contador en el array de dependencias, el useEffect no se vuelve a ejecutar. La función que capturó en el Renderizado 1 sigue creyendo que contador es 0. Esto conduce a un comportamiento incorrecto (o bug) donde el código parece operar con datos viejos.

Al incluir contador en las dependencias, le dices a React: "Cada vez que contador cambie (de 0 a 1, de 1 a 2, etc.), por favor, ejecuta este efecto de nuevo para que capture el nuevo valor."


Las Tres Excepciones (y Cómo Manejarlas)

Si bien la regla de incluir todas las dependencias es cierta, hay situaciones específicas donde puedes o debes omitir una variable, pero esto requiere aplicar otros hooks para mantener la estabilidad.

1. Variables Globales y Funciones Puras Externas

Si utilizas una variable o una función declarada fuera del componente (por ejemplo, importada de otro módulo, como Math.random() o una función pura), no tienes que incluirla en las dependencias, ya que su valor nunca cambia entre renderizados.

2. Funciones Setter de Estado

Las funciones que React te devuelve de useState (ej. setContador, setDatos) están garantizadas para ser estables por React. Nunca cambian.

Setter¿Debe incluirse en useEffect?
setNombreNo. React garantiza su estabilidad.

3. Funciones (Callbacks)

Si utilizas una función declarada dentro del componente (como la función handleGuardar en un formulario), su referencia cambia en cada renderizado (lo que rompe la regla de dependencia).

La Solución: En lugar de ignorarla (lo que activaría la advertencia de React), debes memoizarla usando useCallback.

JavaScript
// Paso 1: Memoizar la función
const handleGuardar = useCallback(() => {
    // Lógica que usa estado o props
}, [prop1, estadoA]); // Dependencias de la función

// Paso 2: Usarla como dependencia en el efecto
useEffect(() => {
    // Suscribirse al guardar...
    listener.subscribe(handleGuardar);

    return () => {
        listener.unsubscribe(handleGuardar);
    };
}, [handleGuardar]); // 👈 La incluimos, pero su valor ya es estable.

Al envolver la función con useCallback, garantizas su estabilidad, y por lo tanto, useEffect solo se ejecuta si las dependencias subyacentes de esa función (prop1 o estadoA) realmente cambian.


Conclusión: La regla de oro es: si usas una variable dentro de useEffect, ponla en el array de dependencias. Si React te da una advertencia (lo cual es bueno, ya que te ayuda a evitar bugs), es señal de que debes memoizar esa dependencia con useCallback o useMemo para estabilizarla antes de incluirla.