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 referencia de memoria, no por contenido (shallow comparison). Cuando haces:
const [obj, setObj] = useState({});
useEffect(() => {
setObj({});
}, [obj]);
Cada llamada a setObj({}) crea un nuevo objeto en memoria, por lo que obj cambia de referencia en cada render. React detecta que [obj] ha cambiado (porque es una referencia diferente), ejecuta el efecto, que a su vez llama a setObj({}), creando otro nuevo objeto y reiniciando el ciclo.
Las primitivas (string, number, boolean) funcionan porque se comparan por valor, no por referencia.
Pasos para solucionarlo
Opción 1: Eliminar dependencias si no necesitas reactividad (recomendada para casos simples)
Si solo necesitas ejecutar la operación una vez al montar el componente:
useEffect(() => {
setIngredients({});
}, []);
Opción 2: Comparar contenido profundo con useDeepCompareEffect
Instala la librería use-deep-compare-effect:
npm install use-deep-compare-effect
Y usa:
import { useEffect } from 'react';
import useDeepCompareEffect from 'use-deep-compare-effect';
useDeepCompareEffect(() => {
setIngredients({});
}, [ingredients]);
Opción 3: Normalizar el estado y comparar propiedades específicas
En lugar de depender del objeto completo, extrae las propiedades relevantes:
const [ingredients, setIngredients] = useState({});
const ingredientCount = Object.keys(ingredients).length;
useEffect(() => {
if (ingredientCount > 0) {
setIngredients({});
}
}, [ingredientCount]);
Opción 4: Usar useReducer para gestión compleja de estado
const initialState = {};
function reducer(state, action) {
switch (action.type) {
case 'reset':
return {};
default:
return state;
}
}
const [ingredients, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
dispatch({ type: 'reset' });
}, []);
Bloque de código corregido (recomendado)
Para el caso específico del usuario (resetear estado a objeto vacío):
const [ingredients, setIngredients] = useState({});
// Solución definitiva: solo ejecutar una vez al montar
useEffect(() => {
setIngredients({});
}, []);
Si necesitas lógica condicional más compleja:
const [ingredients, setIngredients] = useState({});
const ingredientsJSON = JSON.stringify(ingredients);
useEffect(() => {
// Solo resetear si hay ingredientes
if (ingredientsJSON !== '{}') {
setIngredients({});
}
}, [ingredientsJSON]);
Pro-tip
-
Nunca uses
JSON.stringifyen dependencias de alto rendimiento (componentes que renderizan frecuentemente), ya que es costoso. Usa comparación manual o librerías optimizadas comofast-deep-equal. - Para objetos/arrays complejos, considera usar immutabilidad y patrones como
immerpara evitar recreaciones innecesarias. - Si el objetivo es solo limpiar el estado al desmontar, usa el cleanup function:
useEffect(() => {
return () => {
setIngredients({});
};
}, []);
Top comments (0)