DEV Community

Erick Eduardo Ramos
Erick Eduardo Ramos

Posted on

Cómo solucionar el bucle infinito en `useEffect` con objetos y arrays

Cómo solucionar el bucle infinito en useEffect con objetos y arrays

Explicación técnica

El problema ocurre porque useEffect compara los valores de las dependencias usando comparación de referencia (===), no por contenido (shallow comparison). Cuando usas useState({}), cada llamada a setObj({}) crea un nuevo objeto en memoria, aunque tenga el mismo contenido. Por lo tanto:

  • {} === {}false
  • [1,2] === [1,2]false

En tu caso:

const [ingredients, setIngredients] = useState({});
useEffect(() => {
  setIngredients({}); // ← ¡Crea un nuevo objeto!
}, [ingredients]); // ← Siempre es una referencia distinta → loop infinito
Enter fullscreen mode Exit fullscreen mode

React detecta que la referencia cambia y vuelve a ejecutar el efecto, que a su vez vuelve a cambiar la referencia, creando un bucle infinito.


Solución definitiva (3 enfoques)

✅ Opción 1: Evitar la actualización innecesaria (recomendada)

Si no necesitas actualizar el estado (como en tu ejemplo donde pones {} en {}), no llames al setter:

useEffect(() => {
  // Si el estado ya es {}, no necesitas hacer nada
  // Solo ejecuta lógica si hay algo que hacer
}, []); // Dependencia vacía: solo corre una vez al montar
Enter fullscreen mode Exit fullscreen mode

✅ Opción 2: Comparación por contenido con useDeepCompareEffect

Crea un hook personalizado que compare por profundidad (deep comparison):

import { useEffect, useRef } from 'react';

function useDeepCompareEffect(callback, dependencies) {
  const previousDeps = useRef(dependencies);

  const changedDeps = dependencies.some((dep, i) => 
    !Object.is(dep, previousDeps.current[i])
  );

  if (changedDeps) {
    previousDeps.current = dependencies;
    useEffect(callback, dependencies);
  }
}

// Uso:
useDeepCompareEffect(() => {
  setIngredients({});
}, [ingredients]);
Enter fullscreen mode Exit fullscreen mode

⚠️ Nota: Usa librerías como use-deep-compare-effect en producción para evitar bugs.

✅ Opción 3: Normalizar el estado y usar valores primitivos

Convierte objetos/array en valores primitivos para comparación:

const [ingredients, setIngredients] = useState([]);
const [ingredientsCount, setIngredientsCount] = useState(0);

useEffect(() => {
  // Solo actualiza si el conteo cambia
  if (ingredients.length !== ingredientsCount) {
    setIngredientsCount(ingredients.length);
    // Tu lógica aquí
  }
}, [ingredientsCount, ingredients.length]);
Enter fullscreen mode Exit fullscreen mode

Bloque de código corregido (ejemplo práctico)

Antes (bucle infinito):

const [ingredients, setIngredients] = useState({});

useEffect(() => {
  setIngredients({}); // ← ¡Causa loop!
}, [ingredients]);
Enter fullscreen mode Exit fullscreen mode

Después (solución limpia):

const [ingredients, setIngredients] = useState({});

useEffect(() => {
  // Solo ejecutar si hay lógica real que necesite ejecutarse
  // Si solo quieres inicializar, usa una bandera o dependencia vacía
}, []); // Dependencia vacía: ejecuta una vez al montar
Enter fullscreen mode Exit fullscreen mode

O si necesitas resetear bajo condiciones específicas:

const [ingredients, setIngredients] = useState({});
const [resetTrigger, setResetTrigger] = useState(false);

useEffect(() => {
  if (resetTrigger) {
    setIngredients({});
    setResetTrigger(false); // Resetear la bandera
  }
}, [resetTrigger]);
Enter fullscreen mode Exit fullscreen mode

Pro-tip: Regla de oro para hooks con objetos/array

Nunca uses objetos/array como dependencias directas si vas a reasignarlos dentro del useEffect.

En su lugar:

  • Usa valores primitivos (IDs, longitudes, timestamps)
  • Implementa comparación profunda con librerías probadas
  • Reconsidera si realmente necesitas actualizar el estado (¿ya tiene el valor que quieres asignar?)

Bonus: Si usas immer o useImmer, puedes evitar este problema con inmutabilidad controlada.

Top comments (0)