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 (shallow comparison). Cuando usas useState({}), cada llamada a setObj({}) crea un nuevo objeto en memoria, aunque tenga el mismo contenido. Por tanto:
-
{} === {}→false -
[1,2] === [1,2]→false
En tu caso:
const [ingredients, setIngredients] = useState({});
useEffect(() => {
setIngredients({}); // ← ¡Nuevo objeto en memoria!
}, [ingredients]); // ← Siempre cambia la referencia → bucle infinito
React detecta que la referencia de ingredients cambia en cada render, vuelve a ejecutar el useEffect, que vuelve a cambiar la referencia, y así sucesivamente.
Solución definitiva (3 enfoques)
✅ Opción 1: Evitar el cambio innecesario (recomendada)
Si solo quieres inicializar el estado una vez, usa un array vacío como dependencia:
useEffect(() => {
setIngredients({});
}, []); // ← Solo se ejecuta en mount
✅ Opción 2: Comparación profunda manual con useDeepCompareEffect
Crea un hook personalizado que compare por contenido (usando JSON.stringify o librerías como lodash.isequal):
import { useEffect, useRef } from 'react';
function useDeepCompareEffect(callback, dependencies) {
const ref = useRef();
if (!ref.current) {
ref.current = { deps: dependencies, value: JSON.stringify(dependencies) };
}
const currentString = JSON.stringify(dependencies);
if (ref.current.value !== currentString) {
ref.current = { deps: dependencies, value: currentString };
callback();
}
}
// Uso:
useDeepCompareEffect(() => {
setIngredients({});
}, [ingredients]);
⚠️ Nota:
JSON.stringifytiene limitaciones (orden de propiedades, funciones,undefined, etc.). Para casos críticos, usalodash.isequal.
✅ Opción 3: Actualizar solo si es necesario (patrón funcional)
Verifica si hay cambios reales antes de llamar al setter:
useEffect(() => {
if (Object.keys(ingredients).length > 0) {
setIngredients({});
}
}, [ingredients]);
O con arrays:
useEffect(() => {
if (ingredients.length > 0) {
setIngredients([]);
}
}, [ingredients]);
Pro-tip: Evita este patrón en producción
La lógica de "resetear a estado inicial dentro de un useEffect que depende del estado" es una mala práctica. En su lugar:
- Inicializa correctamente el estado:
const [ingredients, setIngredients] = useState(initialIngredients);
- Crea una función explícita para resetear:
const resetIngredients = () => setIngredients({});
-
Llama a
resetIngredientsdonde sea necesario (en eventos, no enuseEffect).
Esto hace el flujo de datos más predecible y evita efectos secundarios ocultos.
Top comments (0)