Cómo solucionar el bucle infinito en useEffect con objetos y arrays
Explicación técnica (por qué ocurre)
El problema ocurre porque React compara los dependencies del useEffect usando comparación === (igualdad estricta), no una comparación profunda del contenido.
Cuando haces:
const [obj, setObj] = useState({});
useEffect(() => {
setObj({});
}, [obj]);
Cada vez que setObj({}) se ejecuta, crea un nuevo objeto en memoria. Aunque el contenido parece idéntico, {} !== {} siempre es true. Por lo tanto:
-
useEffectdetecta queobjha cambiado (nueva referencia) - Ejecuta la callback →
setObj({})crea otro nuevo objeto - React vuelve a detectar cambio de referencia → bucle infinito
Solución definitiva (3 enfoques prácticos)
✅ Opción 1: Evita el ciclo usando dependencias vacías (si solo necesitas ejecutar al montar)
useEffect(() => {
setIngredients({});
}, []); // Solo se ejecuta una vez al montar el componente
✅ Opción 2: Usa comparación profunda con JSON.stringify (para efectos que dependen del contenido)
useEffect(() => {
// Solo ejecutar si el contenido de ingredients ha cambiado
if (JSON.stringify(ingredients) !== JSON.stringify(prevIngredientsRef.current)) {
// Tu lógica aquí
}
}, [ingredients]);
⚠️ Nota:
JSON.stringifytiene limitaciones (no preserva funciones,undefined,Symbol, orden de claves no garantizado). Úsalo solo para objetos simples.
✅ Opción 3: Usa useReducer + estado inmutable (recomendado para lógica compleja)
const initialState = {};
const reducer = (state, action) => {
switch (action.type) {
case 'reset':
return {}; // Devuelve nuevo objeto, pero controlado
default:
return state;
}
};
const [ingredients, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
dispatch({ type: 'reset' });
}, []); // O con lógica específica según tu caso
Bloque de código corregido (ejemplo práctico)
Antes (bucle infinito):
const [ingredients, setIngredients] = useState({});
useEffect(() => {
setIngredients({}); // ❌ Crea nuevo objeto → bucle infinito
}, [ingredients]); // ❌ Referencia cambia siempre
Después (solución limpia):
const [ingredients, setIngredients] = useState({});
useEffect(() => {
// ✅ Solo resetear si no está vacío (evita ciclos innecesarios)
if (Object.keys(ingredients).length > 0) {
setIngredients({});
}
}, [ingredients]);
Pro-tip: Patrón para evitar este error en producción
-
Nunca uses objetos/arrays literales como dependencias directas (
[{}],[[]]) -
Usa
useRefpara valores que no deben desencadenar re-renders:
const ingredientsRef = useRef({});
useEffect(() => {
ingredientsRef.current = {}; // Modifica sin causar re-renders
}, []);
-
Para objetos complejos, usa librerías como
immerouseDeepCompareEffect(deuse-deep-compare-effect):
import useDeepCompareEffect from 'use-deep-compare-effect';
useDeepCompareEffect(() => {
setIngredients({});
}, [ingredients]); // ✅ Compara contenido, no referencias
🔥 Regla de oro: Si tu
useEffectmodifica el mismo estado que usa como dependencia, siempre debes verificar si el cambio es necesario antes de llamar al setter.
Top comments (0)