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 superficial (shallow comparison), no profunda. Cuando pasas un objeto o array como dependencia (ej. [obj]), React compara si la referencia en memoria es la misma, no si su contenido es idéntico.
En tu caso:
const [obj, setObj] = useState({});
useEffect(() => {
setObj({}); // ¡Crea un NUEVO objeto en memoria!
}, [obj]); // obj anterior ≠ nuevo objeto → efecto se dispara infinitamente
Cada llamada a setObj({}) genera un objeto nuevo en memoria. Aunque su contenido es idéntico, su referencia es diferente, por lo que React detecta un "cambio" y vuelve a ejecutar el efecto.
Solución definitiva
Opción 1: Evitar mutaciones innecesarias (recomendada)
Solo actualiza el estado si realmente hay un cambio significativo:
useEffect(() => {
// Solo actualiza si el objeto no es el esperado
if (Object.keys(ingredients).length > 0) {
setIngredients({});
}
}, [ingredients]);
Opción 2: Usar comparación profunda con useDeepCompareEffect (recomendada para casos complejos)
Instala la librería use-deep-compare-effect:
npm install use-deep-compare-effect
import useDeepCompareEffect from 'use-deep-compare-effect';
useDeepCompareEffect(() => {
setIngredients({});
}, [ingredients]);
Opción 3: Normalizar el estado (recomendada para estado complejo)
En lugar de guardar objetos completos, guarda solo los valores primitivos necesarios:
// En lugar de: const [ingredients, setIngredients] = useState({});
const [ingredientCount, setIngredientCount] = useState(0);
useEffect(() => {
if (ingredientCount > 0) {
setIngredientCount(0); // Primitivo → comparación por valor
}
}, [ingredientCount]);
Bloque de código corregido
Versión más limpia y segura:
const [ingredients, setIngredients] = useState({});
useEffect(() => {
// Evita el bucle infinito verificando si hay algo que limpiar
if (Object.keys(ingredients).length > 0) {
setIngredients({});
}
}, [ingredients]);
Para casos donde necesitas limpiar siempre (ej. reiniciar después de una operación):
const [shouldReset, setShouldReset] = useState(false);
useEffect(() => {
if (shouldReset) {
setIngredients({});
setShouldReset(false); // Resetear bandera explícitamente
}
}, [shouldReset]);
Pro-tip
-
Nunca uses objetos/arrays literales como dependencias si van a cambiar:
[obj]→[obj.id, obj.version] -
Para arrays: compara longitudes o usa
JSON.stringify()solo en desarrollo (no en producción por rendimiento) - Patrón recomendado: usa banderas booleanas para operaciones de "reset"
-
React 18+: considera usar
useReducerpara lógica de estado compleja con objetos
⚠️ Evita
useEffect(() => {}, [])como solución general: Esto elimina la lógica de actualización por completo y puede causar bugs de stale state. La solución debe abordar la raíz del problema: la comparación de referencias vs contenido.
Top comments (0)