Cómo solucionar el bucle infinito en useEffect con objetos y arrays en React
Explicación técnica (por qué ocurre)
El problema ocurre porque React compara las dependencias del useEffect usando comparación === (igualdad estricta), no por contenido (deep equality). 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 comparaobj1 === obj2→ siemprefalse - Esto dispara el
useEffectinfinitamente → actualiza estado → re-renders → nuevo objeto → efecto se dispara de nuevo
Este comportamiento es intencional en React para evitar costosas comparaciones profundas por defecto.
Pasos para solucionarlo
Opción 1: Evitar la actualización innecesaria (recomendada)
No vuelvas a establecer el mismo valor si no es necesario. Usa una condición para evitar la actualización:
useEffect(() => {
// Solo actualiza si el objeto no está vacío
if (Object.keys(ingredients).length > 0) {
setIngredients({});
}
}, [ingredients]);
Opción 2: Usar comparación profunda manual (cuando necesitas reaccionar a cambios específicos)
Implementa una comparación profunda con JSON.stringify (solo para objetos simples sin funciones/circularidades):
useEffect(() => {
const prev = JSON.stringify(ingredients);
// Simula comparación profunda
if (prev !== JSON.stringify({})) {
setIngredients({});
}
}, [ingredients]);
Opción 3: Usar useMemo para crear una referencia estable (patrón avanzado)
Si necesitas mantener una referencia estable para evitar re-renders:
const emptyIngredients = useMemo(() => ({}), []);
useEffect(() => {
setIngredients(emptyIngredients);
}, [emptyIngredients]);
Opción 4: Separar dependencias (patrón más limpio)
Divide el estado en valores primitivos cuando sea posible:
const [count, setCount] = useState(0);
const [list, setList] = useState([]);
useEffect(() => {
if (count > 0 || list.length > 0) {
setCount(0);
setList([]);
}
}, [count, list]);
Bloque de código corregido (solución definitiva)
Caso de uso típico: limpiar estado al montar el componente
import { useState, useEffect } from 'react';
function MyComponent() {
const [ingredients, setIngredients] = useState({});
useEffect(() => {
// Inicializa con valores por defecto solo al montar
setIngredients({});
}, []); // ← Dependencia vacía: solo se ejecuta una vez
return (
<div>
<pre>{JSON.stringify(ingredients, null, 2)}</pre>
</div>
);
}
Caso de uso: limpiar estado cuando cambia una condición específica
useEffect(() => {
// Solo limpiar si hay datos
if (Object.keys(ingredients).length > 0) {
setIngredients({});
}
}, [ingredients]);
Pro-tip: Buenas prácticas con objetos y arrays en hooks
-
Nunca uses objetos/array literales como dependencias (
[{}],[{...}]) -
Para comparaciones profundas, usa librerías como
fast-deep-equal
import deepEqual from 'fast-deep-equal';
useEffect(() => {
if (!deepEqual(ingredients, {})) {
setIngredients({});
}
}, [ingredients]);
-
Considera usar
useReducerpara estados complejos
const initialState = { ingredients: {} };
const reducer = (state, action) => {
switch (action.type) {
case 'reset': return { ...state, ingredients: {} };
default: return state;
}
};
const [state, dispatch] = useReducer(reducer, initialState);
-
Siempre pregunta: ¿realmente necesito este
useEffect? Muchas veces la lógica puede moverse a eventos directos (onClick,onChange)
⚠️ Advertencia crítica:
JSON.stringifyes costoso en rendimiento. Úsalo solo para objetos pequeños y nunca en componentes que rendericen frecuentemente.
Top comments (0)