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 por 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] siempre cambia de referencia, incluso si el contenido es idéntico
  • React detecta un "cambio" y vuelve a ejecutar el useEffect
  • Esto dispara setObj({}) de nuevo → bucle infinito

Este comportamiento es intencional en React para evitar costosas comparaciones profundas por defecto.


Solución definitiva (3 enfoques prácticos)

✅ Opción 1: Usar useReducer (recomendado para objetos complejos)

import { useReducer } from 'react';

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

function Component() {
  const [state, dispatch] = useReducer(reducer, initialState);

  useEffect(() => {
    dispatch({ type: 'reset' });
    // No necesitas dependencias → se ejecuta solo en montaje
  }, []); // ✅ Sin bucle infinito
}
Enter fullscreen mode Exit fullscreen mode

✅ Opción 2: Comparación manual con useRef (para casos simples)

import { useState, useEffect, useRef } from 'react';

function Component() {
  const [ingredients, setIngredients] = useState({});
  const prevIngredientsRef = useRef();

  useEffect(() => {
    // Guardar valor actual antes de comparar
    prevIngredientsRef.current = ingredients;
  }, [ingredients]);

  useEffect(() => {
    // Solo ejecutar si el contenido es diferente
    if (JSON.stringify(ingredients) !== JSON.stringify(prevIngredientsRef.current)) {
      setIngredients({});
    }
  }, [ingredients]);
}
Enter fullscreen mode Exit fullscreen mode

✅ Opción 3: Evitar mutaciones innecesarias (patrón más limpio)

useEffect(() => {
  // Solo actualizar si no está vacío
  if (Object.keys(ingredients).length > 0) {
    setIngredients({});
  }
}, [ingredients]);
Enter fullscreen mode Exit fullscreen mode

Pro-tip: Regla de oro para dependencias con objetos/arrays

Nunca incluyas objetos/arrays directamente en las dependencias si vas a reasignarlos con el mismo valor.

En su lugar:

  • Usa useReducer para lógica compleja
  • Compara propiedades específicas (length, id, timestamp)
  • Usa useMemo para crear referencias estables
  • Considera si realmente necesitas ese useEffect (¿puede moverse al evento que lo dispara?)

Ejemplo de solución óptima para tu caso:

// Si solo necesitas resetear al montar el componente:
useEffect(() => {
  setIngredients({});
}, []); // ✅ Dependencia vacía → ejecuta una vez

// Si necesitas resetear bajo condiciones específicas:
const resetIngredients = useCallback(() => {
  setIngredients({});
}, []);

useEffect(() => {
  if (someCondition) {
    resetIngredients();
  }
}, [someCondition, resetIngredients]);
Enter fullscreen mode Exit fullscreen mode

⚠️ Evita JSON.stringify en dependencias (causa re-renders innecesarios y es ineficiente en objetos grandes).

Top comments (0)