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 React compara las dependencias del useEffect mediante comparación de referencia (===), no por contenido. Cuando usas useState({}), cada llamada a setObj({}) crea un nuevo objeto en memoria, aunque tenga el mismo contenido. Por lo tanto:

  • [obj] como dependencia → React compara obj con el obj anterior
  • Como son objetos distintos en memoria → la comparación falla → useEffect se ejecuta
  • El useEffect llama a setObj({}) → crea otro objeto nuevo → ciclo infinito

Este comportamiento no ocurre con primitivas (string, number, boolean) porque se comparan por valor, no por referencia.


Pasos para solucionarlo

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

Si no necesitas actualizar el estado cuando el contenido es idéntico, no llames al setter dentro del useEffect si no hay cambios reales.

// ❌ PROBLEMÁTICO: siempre crea un nuevo objeto
useEffect(() => {
  setIngredients({});
}, [ingredients]);
Enter fullscreen mode Exit fullscreen mode
// ✅ SOLUCIÓN: solo actualizar si hay cambios reales
useEffect(() => {
  // Si ya está vacío, no hacer nada
  if (Object.keys(ingredients).length === 0) return;

  // Solo si hay datos, resetear
  setIngredients({});
}, [ingredients]);
Enter fullscreen mode Exit fullscreen mode

Opción 2: Usar comparación profunda manual

Si necesitas detectar cambios en el contenido del objeto/array:

import { useEffect, useRef } from 'react';

// Hook personalizado para comparación profunda
function useDeepCompareEffect(callback, dependencies) {
  const previous = useRef();

  if (!previous.current) {
    previous.current = dependencies;
  } else {
    const isDeepEqual = (a, b) => {
      if (a === b) return true;
      if (typeof a !== 'object' || typeof b !== 'object') return false;
      if (Array.isArray(a) !== Array.isArray(b)) return false;

      const keysA = Object.keys(a);
      const keysB = Object.keys(b);
      if (keysA.length !== keysB.length) return false;

      return keysA.every(key => isDeepEqual(a[key], b[key]));
    };

    if (isDeepEqual(dependencies, previous.current)) {
      return;
    }
    previous.current = dependencies;
  }

  useEffect(callback, dependencies);
}

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

Opción 3: Usar useMemo para mantener la misma referencia

Si el objeto es calculado, memoízalo para evitar referencias nuevas:

const emptyIngredients = useMemo(() => ({}), []);

useEffect(() => {
  setIngredients(emptyIngredients);
}, [emptyIngredients]);
Enter fullscreen mode Exit fullscreen mode

Bloque de código corregido (solución más simple)

import { useState, useEffect } from 'react';

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

  // ✅ Solución definitiva: no depender del objeto completo
  useEffect(() => {
    // Solo ejecutar una vez al montar el componente
    // o con lógica específica que no cause re-renders
    setIngredients({});
  }, []); // ← Dependencia vacía: solo se ejecuta en mount/unmount

  return (
    <div>
      <p>Ingredients: {JSON.stringify(ingredients)}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Pro-tip: Mejores prácticas con objetos y arrays en hooks

  1. Evita crear nuevos objetos/array en las dependencias si no es necesario
  2. Usa primitivas como dependencias cuando sea posible (ej: length, id, timestamp)
  3. Para objetos complejos, usa useMemo o useCallback para mantener referencias estables
  4. Considera usar immer o librerías de estado inmutables si manejas estructuras complejas
  5. Si solo necesitas resetear el estado, usa un key único en el componente:
   <MyComponent key={resetKey} />
   // Al cambiar resetKey, React remonta el componente y reinicia su estado
Enter fullscreen mode Exit fullscreen mode

⚠️ Importante: El useEffect con dependencia vacía [] no es una solución universal. Asegúrate de que tu lógica realmente solo debe ejecutarse una vez al montar el componente. Si necesitas reaccionar a cambios específicos, usa la Opción 2 o 3.

Top comments (0)