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 React compara las dependencias del useEffect usando comparación de referencia (===), no por valor. Cuando usas useState({}), cada llamada a setObj({}) crea un nuevo objeto en memoria, aunque tenga el mismo contenido. Al pasar [obj] como dependencia, React detecta que la referencia del objeto cambia en cada render (porque es un objeto distinto), lo que dispara el useEffect de nuevo, creando un bucle infinito.

Este comportamiento es intencional: los objetos y arrays son tipos por referencia en JavaScript, y React no hace deep comparison por rendimiento.

Pasos para solucionarlo

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

No actualices el estado si no hay cambios reales. Usa una condición para verificar si realmente necesitas hacer el set:

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

Opción 2: Usar useMemo para mantener la misma referencia

Si necesitas mantener un objeto/array constante en memoria:

const emptyIngredients = useMemo(() => ({}), []);

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

Opción 3: Comparar contenido manualmente con JSON.stringify (solo para estructuras simples)

⚠️ Solo para objetos planos sin funciones ni métodos

useEffect(() => {
  const current = JSON.stringify(ingredients);
  const target = JSON.stringify({});

  if (current !== target) {
    setIngredients({});
  }
}, [ingredients]);
Enter fullscreen mode Exit fullscreen mode

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

Para estados complejos, useReducer es más adecuado y evita este tipo de problemas:

const initialState = { items: {} };

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

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

useEffect(() => {
  // Lógica que decide cuándo resetear
  if (shouldReset(state.items)) {
    dispatch({ type: 'reset' });
  }
}, [state.items]);
Enter fullscreen mode Exit fullscreen mode

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

La solución más limpia y performante para tu caso específico:

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

useEffect(() => {
  // Solo actualizar si hay algo que resetear
  if (Object.keys(ingredients).length > 0) {
    setIngredients({});
  }
  // No incluir ingredients en las dependencias para evitar el bucle
}, []);
Enter fullscreen mode Exit fullscreen mode

Pro-tip

  • Nunca incluyas objetos/arrays directamente como dependencias si vas a crear nuevos objetos en el useEffect
  • Usa useMemo para mantener referencias estables cuando necesites comparar objetos
  • Para arrays, considera usar useMemo con JSON.stringify o bibliotecas como immer para manejo de estado inmutable
  • Si el estado es complejo, useReducer es casi siempre mejor que múltiples useState con efectos interdependientes

Top comments (0)