DEV Community

Erick Eduardo Ramos
Erick Eduardo Ramos

Posted on

Cómo solucionar el bucle infinito en useEffect con objetos y arrays en React

Cómo solucionar el bucle infinito en useEffect con objetos y arrays en React

Explicación técnica (por qué ocurre)

El problema ocurre porque React compara las dependencias del useEffect usando comparación === (igualdad estricta), no por contenido (deep equality). Cuando usas useState({}), cada llamada a setObj({}) crea un nuevo objeto en memoria, aunque tenga el mismo contenido. Por lo tanto:

  • [obj] como dependencia → React compara obj1 === obj2 → siempre false
  • Esto dispara el useEffect infinitamente → actualiza estado → re-renders → nuevo objeto → efecto se dispara de nuevo

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


Pasos para solucionarlo

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

No vuelvas a establecer el mismo valor si no es necesario. Usa una condición para evitar la actualización:

useEffect(() => {
  // Solo actualiza 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 comparación profunda manual (cuando necesitas reaccionar a cambios específicos)

Implementa una comparación profunda con JSON.stringify (solo para objetos simples sin funciones/circularidades):

useEffect(() => {
  const prev = JSON.stringify(ingredients);
  // Simula comparación profunda
  if (prev !== JSON.stringify({})) {
    setIngredients({});
  }
}, [ingredients]);
Enter fullscreen mode Exit fullscreen mode

Opción 3: Usar useMemo para crear una referencia estable (patrón avanzado)

Si necesitas mantener una referencia estable para evitar re-renders:

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

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

Opción 4: Separar dependencias (patrón más limpio)

Divide el estado en valores primitivos cuando sea posible:

const [count, setCount] = useState(0);
const [list, setList] = useState([]);

useEffect(() => {
  if (count > 0 || list.length > 0) {
    setCount(0);
    setList([]);
  }
}, [count, list]);
Enter fullscreen mode Exit fullscreen mode

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

Caso de uso típico: limpiar estado al montar el componente

import { useState, useEffect } from 'react';

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

  useEffect(() => {
    // Inicializa con valores por defecto solo al montar
    setIngredients({});
  }, []); // ← Dependencia vacía: solo se ejecuta una vez

  return (
    <div>
      <pre>{JSON.stringify(ingredients, null, 2)}</pre>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Caso de uso: limpiar estado cuando cambia una condición específica

useEffect(() => {
  // Solo limpiar si hay datos
  if (Object.keys(ingredients).length > 0) {
    setIngredients({});
  }
}, [ingredients]);
Enter fullscreen mode Exit fullscreen mode

Pro-tip: Buenas prácticas con objetos y arrays en hooks

  1. Nunca uses objetos/array literales como dependencias ([{}], [{...}])
  2. Para comparaciones profundas, usa librerías como fast-deep-equal
   import deepEqual from 'fast-deep-equal';

   useEffect(() => {
     if (!deepEqual(ingredients, {})) {
       setIngredients({});
     }
   }, [ingredients]);
Enter fullscreen mode Exit fullscreen mode
  1. Considera usar useReducer para estados complejos
   const initialState = { ingredients: {} };
   const reducer = (state, action) => {
     switch (action.type) {
       case 'reset': return { ...state, ingredients: {} };
       default: return state;
     }
   };

   const [state, dispatch] = useReducer(reducer, initialState);
Enter fullscreen mode Exit fullscreen mode
  1. Siempre pregunta: ¿realmente necesito este useEffect? Muchas veces la lógica puede moverse a eventos directos (onClick, onChange)

⚠️ Advertencia crítica: JSON.stringify es costoso en rendimiento. Úsalo solo para objetos pequeños y nunca en componentes que rendericen frecuentemente.

Top comments (0)