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 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 tanto:

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

En tu caso:

const [ingredients, setIngredients] = useState({});
useEffect(() => {
  setIngredients({}); // ← ¡Nuevo objeto en memoria!
}, [ingredients]);     // ← Siempre cambia la referencia → bucle infinito
Enter fullscreen mode Exit fullscreen mode

React detecta que la referencia de ingredients cambia en cada render, vuelve a ejecutar el useEffect, que vuelve a cambiar la referencia, y así sucesivamente.


Solución definitiva (3 enfoques)

✅ Opción 1: Evitar el cambio innecesario (recomendada)

Si solo quieres inicializar el estado una vez, usa un array vacío como dependencia:

useEffect(() => {
  setIngredients({});
}, []); // ← Solo se ejecuta en mount
Enter fullscreen mode Exit fullscreen mode

✅ Opción 2: Comparación profunda manual con useDeepCompareEffect

Crea un hook personalizado que compare por contenido (usando JSON.stringify o librerías como lodash.isequal):

import { useEffect, useRef } from 'react';

function useDeepCompareEffect(callback, dependencies) {
  const ref = useRef();
  if (!ref.current) {
    ref.current = { deps: dependencies, value: JSON.stringify(dependencies) };
  }

  const currentString = JSON.stringify(dependencies);
  if (ref.current.value !== currentString) {
    ref.current = { deps: dependencies, value: currentString };
    callback();
  }
}

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

⚠️ Nota: JSON.stringify tiene limitaciones (orden de propiedades, funciones, undefined, etc.). Para casos críticos, usa lodash.isequal.

✅ Opción 3: Actualizar solo si es necesario (patrón funcional)

Verifica si hay cambios reales antes de llamar al setter:

useEffect(() => {
  if (Object.keys(ingredients).length > 0) {
    setIngredients({});
  }
}, [ingredients]);
Enter fullscreen mode Exit fullscreen mode

O con arrays:

useEffect(() => {
  if (ingredients.length > 0) {
    setIngredients([]);
  }
}, [ingredients]);
Enter fullscreen mode Exit fullscreen mode

Pro-tip: Evita este patrón en producción

La lógica de "resetear a estado inicial dentro de un useEffect que depende del estado" es una mala práctica. En su lugar:

  1. Inicializa correctamente el estado:
   const [ingredients, setIngredients] = useState(initialIngredients);
Enter fullscreen mode Exit fullscreen mode
  1. Crea una función explícita para resetear:
   const resetIngredients = () => setIngredients({});
Enter fullscreen mode Exit fullscreen mode
  1. Llama a resetIngredients donde sea necesario (en eventos, no en useEffect).

Esto hace el flujo de datos más predecible y evita efectos secundarios ocultos.

Top comments (0)