Cómo solucionar el bucle infinito en useEffect con objetos y arrays
Explicación técnica
El problema ocurre porque useEffect compara los valores de 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. React detecta que la referencia cambia (obj !== obj), por lo que vuelve a ejecutar el efecto, causando el bucle infinito.
// ❌ Problema: cada {} es un nuevo objeto en memoria
const [obj, setObj] = useState({});
useEffect(() => {
setObj({}); // ← ¡Nuevo objeto! → dependencia cambia → efecto se ejecuta de nuevo
}, [obj]);
Pasos para solucionarlo
Opción 1: Evitar la dependencia innecesaria (recomendada)
Si no necesitas reaccionar a cambios en el objeto, elimina la dependencia:
useEffect(() => {
setIngredients({});
}, []); // ✅ Solo se ejecuta una vez al montar el componente
Opción 2: Comparación por contenido con useDeepCompareEffect
Si necesitas reaccionar a cambios reales en el contenido del objeto/array:
- Instala la librería
use-deep-compare-effect:
npm install use-deep-compare-effect
- Usa el hook especial:
import useDeepCompareEffect from 'use-deep-compare-effect';
useDeepCompareEffect(() => {
setIngredients({});
}, [ingredients]); // ✅ Compara contenido, no referencia
Opción 3: Normalizar el estado (patrón recomendado para producción)
En lugar de depender del objeto completo, extrae una clave calculable:
const [ingredients, setIngredients] = useState({});
const ingredientCount = Object.keys(ingredients).length;
useEffect(() => {
if (ingredientCount > 0) {
setIngredients({});
}
}, [ingredientCount]); // ✅ Primitivo → comparación por valor
Opción 4: Usar useReducer para lógica compleja
Para estados complejos, useReducer evita este problema naturalmente:
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' });
}, []); // ✅ Sin dependencias problemáticas
Bloque de código corregido (solución definitiva)
import { useState, useEffect } from 'react';
function MyComponent() {
const [ingredients, setIngredients] = useState({});
// ✅ Solución recomendada: dependencia vacía si solo quieres resetear al montar
useEffect(() => {
setIngredients({});
}, []);
// O si necesitas lógica condicional:
const hasIngredients = Object.keys(ingredients).length > 0;
useEffect(() => {
if (hasIngredients) {
setIngredients({});
}
}, [hasIngredients]); // ✅ Primitivo → no hay bucle infinito
return (
<div>
<p>Ingredientes: {JSON.stringify(ingredients)}</p>
</div>
);
}
Pro-tip
-
Nunca uses objetos/arrays como dependencias directas si vas a modificarlos dentro del mismo
useEffect. -
Preferencia por primitivos (
number,string,boolean) en las dependencias. - Para estados complejos, considera:
- Normalizar datos (extraer claves calculables)
- Usar
useReducerpara lógica de estado compleja - Librerías como
immerpara manipulación inmutable segura
Regla de oro: Si el efecto modifica la misma dependencia que usa, siempre habrá un bucle infinito. Rompe el ciclo eliminando la dependencia o usando comparación por valor.
Top comments (0)