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 (por qué ocurre)

El bucle infinito ocurre porque React compara las dependencias del useEffect usando comparación === (igualdad estricta), no por contenido. Cuando usas useState({}), cada llamada a setObj({}) crea un nuevo objeto en memoria, aunque tenga el mismo contenido. Como {} !== {}, React detecta un cambio y vuelve a ejecutar el useEffect, causando el ciclo infinito.

Este comportamiento es intencional: React asume que si el objeto es distinto en referencia, su contenido podría haber cambiado, y no hace una comparación profunda por rendimiento.


Pasos para solucionarlo

Opción 1: Evitar reasignar el mismo valor (recomendada)

Si el objetivo es inicializar el estado, hazlo solo una vez (en el montaje), no en cada render.

// ✅ CORRECTO: Solo se ejecuta una vez al montar el componente
useEffect(() => {
  setIngredients({});
}, []); // Dependencia vacía: solo en mount/unmount
Enter fullscreen mode Exit fullscreen mode

Opción 2: Comparar contenido manualmente con useDeepCompareEffect

Si necesitas reactividad basada en el contenido del objeto/array:

  1. Instala una librería como use-deep-compare-effect:
   npm install use-deep-compare-effect
Enter fullscreen mode Exit fullscreen mode
  1. Úsala en lugar de useEffect:
   import useDeepCompareEffect from 'use-deep-compare-effect';

   useDeepCompareEffect(() => {
     setIngredients({});
   }, [ingredients]); // Ahora compara contenido, no referencia
Enter fullscreen mode Exit fullscreen mode

Opción 3: Normalizar el estado (patrón recomendado)

Evita objetos/array como dependencias directas. Usa valores primitivos o claves específicas:

// En lugar de depender de todo el objeto
const [ingredients, setIngredients] = useState({});
const [ingredientsCount, setIngredientsCount] = useState(0);

useEffect(() => {
  setIngredients({});
  setIngredientsCount(0); // O actualiza según lógica real
}, [ingredientsCount]); // Dependencia primitiva → no hay bucle infinito
Enter fullscreen mode Exit fullscreen mode

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

Antes (bucle infinito):

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

useEffect(() => {
  setIngredients({}); // ❌ Crea nuevo objeto → nueva referencia → bucle
}, [ingredients]);     // ❌ Siempre cambia la referencia
Enter fullscreen mode Exit fullscreen mode

Después (solución definitiva):

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

// ✅ Opción 1: Si solo quieres limpiar al montar
useEffect(() => {
  setIngredients({});
}, []); // Dependencia vacía

// ✅ Opción 2: Si necesitas limpiar bajo condiciones específicas
const resetIngredients = useCallback(() => {
  setIngredients({});
}, []);

useEffect(() => {
  // Lógica para decidir cuándo resetear (ej: cuando se cumple una condición)
  if (someCondition) {
    resetIngredients();
  }
}, [someCondition, resetIngredients]);
Enter fullscreen mode Exit fullscreen mode

Pro-tip: Evita este error desde el diseño

  1. Usa useReducer para estados complejos: Manejar objetos/array con useReducer evita este problema y mejora la trazabilidad:
   const initialState = {};
   function reducer(state, action) {
     switch (action.type) {
       case 'reset': return {};
       default: return state;
     }
   }
   const [state, dispatch] = useReducer(reducer, initialState);
Enter fullscreen mode Exit fullscreen mode
  1. Sigue el principio de "normalización":

    En lugar de depender de objetos completos, extrae valores clave (IDs, longitudes, hashes) para las dependencias.

  2. Nunca pongas objetos/array literales en las dependencias:

    useEffect(() => {}, [{a: 1}]) siempre causará bucles infinitos.

Regla de oro: Si usas useState({}), el único lugar donde debes llamar setObj({}) es en el useEffect con dependencia vacía [], o dentro de un useCallback/useMemo con dependencias estables.

Top comments (0)