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. React detecta que la referencia cambia (obj !== obj), por lo que vuelve a ejecutar el efecto, causando el bucle infinito.

// ❌ Problema: cada {} es un nuevo objeto en memoria
const [obj, setObj] = useState({});
useEffect(() => {
  setObj({}); // ← ¡Nuevo objeto! → dependencia cambia → efecto se ejecuta de nuevo
}, [obj]);
Enter fullscreen mode Exit fullscreen mode

Pasos para solucionarlo

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

Si no necesitas reaccionar a cambios en el objeto, elimina la dependencia:

useEffect(() => {
  setIngredients({});
}, []); // ✅ Solo se ejecuta una vez al montar el componente
Enter fullscreen mode Exit fullscreen mode

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

Si necesitas reaccionar a cambios reales en el contenido del objeto/array:

  1. Instala la librería use-deep-compare-effect:
npm install use-deep-compare-effect
Enter fullscreen mode Exit fullscreen mode
  1. Usa el hook especial:
import useDeepCompareEffect from 'use-deep-compare-effect';

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

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

En lugar de depender del objeto completo, extrae una clave calculable:

const [ingredients, setIngredients] = useState({});
const ingredientCount = Object.keys(ingredients).length;

useEffect(() => {
  if (ingredientCount > 0) {
    setIngredients({});
  }
}, [ingredientCount]); // ✅ Primitivo → comparación por valor
Enter fullscreen mode Exit fullscreen mode

Opción 4: Usar useReducer para lógica compleja

Para estados complejos, useReducer evita este problema naturalmente:

const initialState = {};
function reducer(state, action) {
  switch (action.type) {
    case 'reset': return {};
    default: return state;
  }
}

const [ingredients, dispatch] = useReducer(reducer, initialState);

useEffect(() => {
  dispatch({ type: 'reset' });
}, []); // ✅ Sin dependencias problemáticas
Enter fullscreen mode Exit fullscreen mode

Bloque de código corregido (solución definitiva)

import { useState, useEffect } from 'react';

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

  // ✅ Solución recomendada: dependencia vacía si solo quieres resetear al montar
  useEffect(() => {
    setIngredients({});
  }, []);

  // O si necesitas lógica condicional:
  const hasIngredients = Object.keys(ingredients).length > 0;

  useEffect(() => {
    if (hasIngredients) {
      setIngredients({});
    }
  }, [hasIngredients]); // ✅ Primitivo → no hay bucle infinito

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

Pro-tip

  • Nunca uses objetos/arrays como dependencias directas si vas a modificarlos dentro del mismo useEffect.
  • Preferencia por primitivos (number, string, boolean) en las dependencias.
  • Para estados complejos, considera:
    • Normalizar datos (extraer claves calculables)
    • Usar useReducer para lógica de estado compleja
    • Librerías como immer para manipulación inmutable segura

Regla de oro: Si el efecto modifica la misma dependencia que usa, siempre habrá un bucle infinito. Rompe el ciclo eliminando la dependencia o usando comparación por valor.

Top comments (0)