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 referencia de memoria, no por contenido (shallow comparison). Cuando haces:

const [obj, setObj] = useState({});
useEffect(() => {
  setObj({});
}, [obj]);
Enter fullscreen mode Exit fullscreen mode

Cada llamada a setObj({}) crea un nuevo objeto en memoria, por lo que obj cambia de referencia en cada render. React detecta que [obj] ha cambiado (porque es una referencia diferente), ejecuta el efecto, que a su vez llama a setObj({}), creando otro nuevo objeto y reiniciando el ciclo.

Las primitivas (string, number, boolean) funcionan porque se comparan por valor, no por referencia.


Pasos para solucionarlo

Opción 1: Eliminar dependencias si no necesitas reactividad (recomendada para casos simples)

Si solo necesitas ejecutar la operación una vez al montar el componente:

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

Opción 2: Comparar contenido profundo con useDeepCompareEffect

Instala la librería use-deep-compare-effect:

npm install use-deep-compare-effect
Enter fullscreen mode Exit fullscreen mode

Y usa:

import { useEffect } from 'react';
import useDeepCompareEffect from 'use-deep-compare-effect';

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

Opción 3: Normalizar el estado y comparar propiedades específicas

En lugar de depender del objeto completo, extrae las propiedades relevantes:

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

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

Opción 4: Usar useReducer para gestión compleja de estado

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' });
}, []);
Enter fullscreen mode Exit fullscreen mode

Bloque de código corregido (recomendado)

Para el caso específico del usuario (resetear estado a objeto vacío):

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

// Solución definitiva: solo ejecutar una vez al montar
useEffect(() => {
  setIngredients({});
}, []);
Enter fullscreen mode Exit fullscreen mode

Si necesitas lógica condicional más compleja:

const [ingredients, setIngredients] = useState({});
const ingredientsJSON = JSON.stringify(ingredients);

useEffect(() => {
  // Solo resetear si hay ingredientes
  if (ingredientsJSON !== '{}') {
    setIngredients({});
  }
}, [ingredientsJSON]);
Enter fullscreen mode Exit fullscreen mode

Pro-tip

  • Nunca uses JSON.stringify en dependencias de alto rendimiento (componentes que renderizan frecuentemente), ya que es costoso. Usa comparación manual o librerías optimizadas como fast-deep-equal.
  • Para objetos/arrays complejos, considera usar immutabilidad y patrones como immer para evitar recreaciones innecesarias.
  • Si el objetivo es solo limpiar el estado al desmontar, usa el cleanup function:
useEffect(() => {
  return () => {
    setIngredients({});
  };
}, []);
Enter fullscreen mode Exit fullscreen mode

Top comments (0)