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 superficial (shallow comparison), no profunda. Cuando pasas un objeto o array como dependencia (ej. [obj]), React compara si la referencia en memoria es la misma, no si su contenido es idéntico.

En tu caso:

const [obj, setObj] = useState({});
useEffect(() => {
  setObj({}); // ¡Crea un NUEVO objeto en memoria!
}, [obj]); // obj anterior ≠ nuevo objeto → efecto se dispara infinitamente
Enter fullscreen mode Exit fullscreen mode

Cada llamada a setObj({}) genera un objeto nuevo en memoria. Aunque su contenido es idéntico, su referencia es diferente, por lo que React detecta un "cambio" y vuelve a ejecutar el efecto.

Solución definitiva

Opción 1: Evitar mutaciones innecesarias (recomendada)

Solo actualiza el estado si realmente hay un cambio significativo:

useEffect(() => {
  // Solo actualiza si el objeto no es el esperado
  if (Object.keys(ingredients).length > 0) {
    setIngredients({});
  }
}, [ingredients]);
Enter fullscreen mode Exit fullscreen mode

Opción 2: Usar comparación profunda con useDeepCompareEffect (recomendada para casos complejos)

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

npm install use-deep-compare-effect
Enter fullscreen mode Exit fullscreen mode
import useDeepCompareEffect from 'use-deep-compare-effect';

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

Opción 3: Normalizar el estado (recomendada para estado complejo)

En lugar de guardar objetos completos, guarda solo los valores primitivos necesarios:

// En lugar de: const [ingredients, setIngredients] = useState({});
const [ingredientCount, setIngredientCount] = useState(0);

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

Bloque de código corregido

Versión más limpia y segura:

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

useEffect(() => {
  // Evita el bucle infinito verificando si hay algo que limpiar
  if (Object.keys(ingredients).length > 0) {
    setIngredients({});
  }
}, [ingredients]);
Enter fullscreen mode Exit fullscreen mode

Para casos donde necesitas limpiar siempre (ej. reiniciar después de una operación):

const [shouldReset, setShouldReset] = useState(false);

useEffect(() => {
  if (shouldReset) {
    setIngredients({});
    setShouldReset(false); // Resetear bandera explícitamente
  }
}, [shouldReset]);
Enter fullscreen mode Exit fullscreen mode

Pro-tip

  1. Nunca uses objetos/arrays literales como dependencias si van a cambiar: [obj][obj.id, obj.version]
  2. Para arrays: compara longitudes o usa JSON.stringify() solo en desarrollo (no en producción por rendimiento)
  3. Patrón recomendado: usa banderas booleanas para operaciones de "reset"
  4. React 18+: considera usar useReducer para lógica de estado compleja con objetos

⚠️ Evita useEffect(() => {}, []) como solución general: Esto elimina la lógica de actualización por completo y puede causar bugs de stale state. La solución debe abordar la raíz del problema: la comparación de referencias vs contenido.

Top comments (0)