Fix React useEffect Infinite Loop: 4 Causes and Fixes
React useEffect infinite loops happen when your effect modifies a value in its own dependency array. Here are the 4 patterns that cause it and the exact fix for each: objects, functions, state, missing deps.
A useEffect infinite loop happens when the effect modifies a value that is also in its dependency array. The effect runs, changes the value, React sees the change, runs the effect again, and the cycle continues until React throws a maximum update depth error.
This runs forever. Four patterns cause this. Each has a different fix.
The Error Message
Or in the browser console:
Pattern 1: State Updated by the Effect Is in Its Own Deps
data changes, effect runs, data changes again. Use the functional update form instead. It does not need data in the dependency array:
Pattern 2: Object or Array as Dependency
Objects and arrays are compared by reference, not value. { category: 'books' } creates a new object on every render. React sees a new reference, treats it as a changed dependency, and runs the effect again.
If the object is static, move it outside the component:
If the values are dynamic, use primitives as dependencies instead of the whole object:
Or memoize the object with useMemo so the reference only changes when the inputs change:
Pattern 3: Function as Dependency
Functions defined inside the component get a new reference on every render. Same problem as objects.
Move the function inside the effect if it does not depend on anything external:
Or wrap in useCallback if the function needs to be reused elsewhere:
Pattern 4: Missing Dependency Array
No dependency array means the effect runs after every single render. Add one:
Note: A
useEffectwith no dependency array is almost never intentional. Always ask when the effect should run. Only once on mount means[]. When something changes means[thatThing]. After every render means no array, which is rarely correct and usually a bug.
How to Diagnose a Loop
Add a ref counter to confirm the loop and find the culprit:
If the count climbs fast, the loop is confirmed. Then log each dependency individually:
The dep that logs a new value on every render is the one causing the loop. React DevTools Profiler also shows re-render frequency. A component re-rendering 50+ times per second is a reliable signal.
Quick Reference
| Cause | Fix |
|---|---|
| State updated by effect is in its own deps | Use functional state update: setState(prev => ...) |
| Object or array in deps | Use primitives or useMemo |
| Function in deps | Move inside effect or wrap in useCallback |
| No dependency array | Add [] or specific deps |
FAQ
Q: ESLint keeps telling me to add a dependency but adding it causes a loop. What do I do?
A: The lint rule is correct that the dep is missing, but the real fix is restructuring so the dep is stable. For objects and functions, that means useMemo or useCallback. For state, that means the functional update form. Disabling the lint rule with eslint-disable hides the symptom without fixing the cause.
Q: Can I use an empty dependency array for an effect that reads state?
A: You can, but the effect will only ever see the initial state value due to closure. Use the functional update form for state updates, or restructure so the effect does not need to read the current value directly.
For complex components where the loop is not obvious, multiple effects, or shared state across hooks, paste the component into DebugAI and it will read the full hook chain and identify which effect-dep pair is cycling.
Debug faster starting today.
Free VS Code extension. 10 sessions/day. No credit card.