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 mediante comparación de referencia (===), no por contenido. 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 comparaobjcon elobjanterior - Como son objetos distintos en memoria → la comparación falla →
useEffectse ejecuta - El
useEffectllama asetObj({})→ crea otro objeto nuevo → ciclo infinito
Este comportamiento no ocurre con primitivas (string, number, boolean) porque se comparan por valor, no por referencia.
Pasos para solucionarlo
Opción 1: Evitar la actualización innecesaria (recomendada)
Si no necesitas actualizar el estado cuando el contenido es idéntico, no llames al setter dentro del useEffect si no hay cambios reales.
// ❌ PROBLEMÁTICO: siempre crea un nuevo objeto
useEffect(() => {
setIngredients({});
}, [ingredients]);
// ✅ SOLUCIÓN: solo actualizar si hay cambios reales
useEffect(() => {
// Si ya está vacío, no hacer nada
if (Object.keys(ingredients).length === 0) return;
// Solo si hay datos, resetear
setIngredients({});
}, [ingredients]);
Opción 2: Usar comparación profunda manual
Si necesitas detectar cambios en el contenido del objeto/array:
import { useEffect, useRef } from 'react';
// Hook personalizado para comparación profunda
function useDeepCompareEffect(callback, dependencies) {
const previous = useRef();
if (!previous.current) {
previous.current = dependencies;
} else {
const isDeepEqual = (a, b) => {
if (a === b) return true;
if (typeof a !== 'object' || typeof b !== 'object') return false;
if (Array.isArray(a) !== Array.isArray(b)) return false;
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
return keysA.every(key => isDeepEqual(a[key], b[key]));
};
if (isDeepEqual(dependencies, previous.current)) {
return;
}
previous.current = dependencies;
}
useEffect(callback, dependencies);
}
// Uso:
useDeepCompareEffect(() => {
setIngredients({});
}, [ingredients]);
Opción 3: Usar useMemo para mantener la misma referencia
Si el objeto es calculado, memoízalo para evitar referencias nuevas:
const emptyIngredients = useMemo(() => ({}), []);
useEffect(() => {
setIngredients(emptyIngredients);
}, [emptyIngredients]);
Bloque de código corregido (solución más simple)
import { useState, useEffect } from 'react';
function MyComponent() {
const [ingredients, setIngredients] = useState({});
// ✅ Solución definitiva: no depender del objeto completo
useEffect(() => {
// Solo ejecutar una vez al montar el componente
// o con lógica específica que no cause re-renders
setIngredients({});
}, []); // ← Dependencia vacía: solo se ejecuta en mount/unmount
return (
<div>
<p>Ingredients: {JSON.stringify(ingredients)}</p>
</div>
);
}
Pro-tip: Mejores prácticas con objetos y arrays en hooks
- Evita crear nuevos objetos/array en las dependencias si no es necesario
-
Usa primitivas como dependencias cuando sea posible (ej:
length,id,timestamp) -
Para objetos complejos, usa
useMemoouseCallbackpara mantener referencias estables -
Considera usar
immero librerías de estado inmutables si manejas estructuras complejas -
Si solo necesitas resetear el estado, usa un
keyúnico en el componente:
<MyComponent key={resetKey} />
// Al cambiar resetKey, React remonta el componente y reinicia su estado
⚠️ Importante: El
useEffectcon dependencia vacía[]no es una solución universal. Asegúrate de que tu lógica realmente solo debe ejecutarse una vez al montar el componente. Si necesitas reaccionar a cambios específicos, usa la Opción 2 o 3.
Top comments (0)