React’s useEffect is a powerful tool, but when objects and arrays enter the dependency array, the hook can spin into an endless loop. This happens because React uses strict equality (===) to compare dependencies—not deep content. Every time you call setState({}) or setState([]), a new object or array is created in memory, making the dependency always appear "new" to the hook. The result? A loop of renders, state updates, and re-renders that never ends.
Why React’s useEffect gets stuck in a loop
React intentionally avoids deep comparisons in hooks to maintain performance. When you write:
useEffect(() => {
setIngredients({});
}, [ingredients]);the hook triggers whenever ingredients changes. But if setIngredients({}) replaces ingredients with a brand-new object, React sees it as a change—even if the content is identical. This creates a cycle:
- Component renders →
useEffectruns →setIngredients({})→ new object → dependency changes →useEffectruns again.
The problem isn’t the hook—it’s how React compares references, not values.
Four proven ways to break the infinite loop
1. Skip updates that don’t change the state (recommended)
The simplest fix is to avoid resetting state unless necessary. Add a condition to prevent unnecessary updates:
useEffect(() => {
// Only reset if the object isn’t already empty
if (Object.keys(ingredients).length > 0) {
setIngredients({});
}
}, [ingredients]);This ensures the hook only runs when ingredients actually contains data worth clearing.
2. Use deep comparison for specific changes
For cases where you need to react to precise content changes, manually compare objects or arrays using JSON.stringify. This works for small, simple structures without functions or circular references:
useEffect(() => {
const prev = JSON.stringify(ingredients);
if (prev !== JSON.stringify({})) {
setIngredients({});
}
}, [ingredients]);⚠️ Warning: JSON.stringify is computationally expensive. Avoid using it in frequently updating components.
3. Stabilize references with useMemo
If your goal is to reset state on mount without triggering re-renders, create a stable reference using useMemo:
const emptyIngredients = useMemo(() => ({}), []);
useEffect(() => {
setIngredients(emptyIngredients);
}, [emptyIngredients]);Here, emptyIngredients is memoized and never changes, so the effect runs once—exactly as intended.
4. Split state into primitives
When possible, avoid objects and arrays in dependencies by using primitive values like numbers or strings. This keeps dependencies simple and predictable:
const [count, setCount] = useState(0);
const [list, setList] = useState([]);
useEffect(() => {
if (count > 0 || list.length > 0) {
setCount(0);
setList([]);
}
}, [count, list]);This approach is cleaner and reduces the risk of infinite loops entirely.
Best practices for objects and arrays in hooks
- Never use object or array literals as dependencies directly (e.g.,
[{}],[{...}]). - For deep equality checks, consider using lightweight libraries like
fast-deep-equalinstead ofJSON.stringify.
import deepEqual from 'fast-deep-equal';
useEffect(() => {
if (!deepEqual(ingredients, {})) {
setIngredients({});
}
}, [ingredients]);- For complex state,
useReduceroften provides better control and avoids dependency issues by consolidating logic into a single reducer:
const initialState = { ingredients: {} };
const reducer = (state, action) => {
switch (action.type) {
case 'reset':
return { ...state, ingredients: {} };
default:
return state;
}
};
const [state, dispatch] = useReducer(reducer, initialState);- Ask yourself: Do I really need this
useEffect?
Many state updates can be handled directly through events like onClick or onChange, eliminating the need for effects entirely.
The bigger picture: writing maintainable effects
While infinite loops are a common pain point, they’re also a symptom of deeper patterns in React development. By focusing on stable references, conditional updates, and primitive state where possible, developers can write components that are not only loop-free but also easier to debug and scale. The key is to align your dependency logic with React’s reference-based comparison model—without fighting it.
Next time you reach for useEffect to manage state, pause and ask: Is this dependency truly necessary? Often, the answer will help you write cleaner, safer, and more performant React code.
AI summary
React’te useEffect’teki sonsuz döngüleri nesne ve dizi bağımlılıklarıyla nasıl durduracağınızı öğrenin. Pratik çözümler ve performans ipuçlarıyla projelerinizi optimize edin.