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 de referencia (===), no por contenido (deep equality). Cuando usas useState({}), cada llamada a setObj({}) crea un nuevo objeto en memoria, por lo que [obj] siempre cambia de referencia aunque su contenido sea idéntico.

En tu caso:

const [obj, setObj] = useState({});
useEffect(() => {
  setObj({}); // ¡Nuevo objeto en memoria!
}, [obj]); // obj referencia al objeto anterior → desencadena re-renders infinitos
Enter fullscreen mode Exit fullscreen mode

React detecta que obj (referencia) ha cambiado → ejecuta el efecto → llama a setObj({}) → crea nuevo objeto → obj cambia de referencia → se repite el ciclo.


Pasos para solucionarlo

Opción 1: Evitar el ciclo con dependencia vacía (si no necesitas reactividad)

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

Opción 2: Comparación profunda manual (si necesitas reactividad)

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

Opción 3: Usar useReducer para estado complejo (recomendado para objetos/arrays)

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

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

useEffect(() => {
  dispatch({ type: 'reset' });
}, [ingredients]);
Enter fullscreen mode Exit fullscreen mode

Opción 4: Normalizar el estado con valores primitivos

const [ingredientsCount, setIngredientsCount] = useState(0);

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

Bloque de código corregido (recomendado)

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

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

  // Solución definitiva: memoizar la función para evitar recreaciones
  const resetIngredients = useCallback(() => {
    setIngredients({});
  }, []);

  useEffect(() => {
    // Solo resetear si el objeto no está vacío
    if (Object.keys(ingredients).length > 0) {
      resetIngredients();
    }
  }, [ingredients, resetIngredients]);

  return (
    /* tu JSX */
  );
}
Enter fullscreen mode Exit fullscreen mode

Pro-tip: Reglas de oro para evitar este error

  1. Nunca uses objetos/arrays literales como dependencias directas

    useEffect(..., [{ a: 1 }]) siempre fallará.

  2. Para objetos/arrays, usa comparación profunda o normaliza el estado

    • JSON.stringify() para objetos simples (evita en paths críticos por rendimiento)
    • Librerías como lodash.isEqual para casos complejos
    • useMemo para calcular hashes o valores primitivos derivados
  3. Preferir useReducer para lógica de estado compleja

    Reduce la dependencia de efectos reactivos y mejora el control del flujo.

  4. Siempre pregunta: "¿Necesito este efecto reaccionar a todos los cambios del objeto o solo a cambios específicos?"

    Filtra las dependencias a los valores primitivos que realmente importan (ej: ingredients.length en lugar de ingredients).

Top comments (0)