React’s “Maximum Update Depth Exceeded”: The useEffect Bug That Caused Hundreds of API Calls
One useEffect dependency caused my React application to make the same API request hundreds of times.
The browser became sluggish, CPU usage went up, and eventually React crashed with:
Maximum update depth exceeded.
At first, I thought the problem was somewhere in my API or state management.
It wasn't.
The problem was hiding inside a small useEffect.
What Happened?
Everything looked normal at first.
The application compiled successfully.
There were no syntax errors.
The UI loaded correctly.
Then I clicked a button.
A few seconds later, the application started slowing down.
I opened Chrome DevTools and checked the Network tab.
That's when I noticed something strange:
The same API request was being sent repeatedly.
Not twice.
Not ten times.
Hundreds of times.
Every response seemed to trigger another render, which triggered another request.
So I started tracing the component to find out what was causing the loop.
The Culprit: useEffect
I eventually found code similar to this:
const [users, setUsers] = useState([]);
useEffect(() => {
fetchUsers().then((data) => {
setUsers(data);
});
}, [users]);
At first glance, the effect looks reasonable.
But look carefully at the dependency:
[users]
The effect depends on users.
But inside the effect, we're also updating users:
setUsers(data);
That creates a cycle.
Why Does It Loop?
Here's what happens:
Component renders
↓
useEffect runs
↓
API request starts
↓
Data comes back
↓
setUsers(data)
↓
users changes
↓
Component renders again
↓
useEffect runs again
↓
Another API request
↓
... repeat ...
Eventually, React throws:
Maximum update depth exceeded
The important thing to remember is:
If an effect updates a state value that the same effect depends on, you may have created a render loop.
The Fix
In this case, the effect only needed to fetch the users when the component mounted.
So the dependency array should not listen to users:
const [users, setUsers] = useState([]);
useEffect(() => {
fetchUsers().then((data) => {
setUsers(data);
});
}, []);
Now the flow becomes:
Component renders
↓
useEffect runs once
↓
API request
↓
setUsers(data)
↓
Component renders
↓
useEffect does NOT run again
Result:
One request instead of hundreds.
The Dependency Array Mental Model
Whenever I write a new useEffect, I now think about it like this:
Dependency changes
↓
Effect runs
↓
Does the effect update that dependency?
↓
YES
↓
Could the effect trigger itself again?
↓
⚠️
That simple check can prevent a lot of debugging pain.
The dependency array isn't just a list of values you happen to use.
It determines when React should run the effect again.
Common useEffect Mistake
A common pattern is:
useEffect(() => {
setSomething(value);
}, [something]);
Whenever you see an effect structured like this, stop and ask:
"What happens after this state changes?"
If changing the state causes the effect to run again, you need to make sure the cycle has a valid stopping condition.
Before vs After
Before
- Browser became unresponsive
- Hundreds of API requests
- High CPU usage
- Repeated renders
Maximum update depth exceeded- Poor user experience
After
- One API request
- Stable rendering
- Smooth navigation
- Lower unnecessary CPU usage
- Cleaner component logic
What I Learned
This bug reinforced a few React fundamentals for me:
1. Every state update can trigger another render.
Calling:
setUsers(data);
doesn't just change a variable. It causes React to render the component again.
2. Dependencies control when an effect runs.
A dependency changing can cause the effect to execute again.
3. Be careful when an effect changes one of its own dependencies.
That combination can easily create an infinite loop.
4. Don't add dependencies blindly.
The goal isn't to make the dependency array longer.
The goal is to correctly describe when the effect needs to run.
Could Monitoring Have Helped?
Performance and error-monitoring tools can make problems like this easier to investigate.
For example, monitoring can help identify:
- Repeated errors
- Excessive network activity
- Performance degradation
- Session behaviour before the crash
That can significantly reduce the time needed to find the root cause.
Final Thought
This wasn't caused by a complicated algorithm or an obscure library.
It came from a tiny dependency mistake:
[users]
That's what makes React bugs like this interesting.
A small change can create a surprisingly large performance problem.
Now, whenever I write a new useEffect, I ask myself one question:
"Could this effect accidentally trigger itself?"
That question has already saved me from repeating this mistake.
Have you ever encountered an infinite useEffect loop? What caused yours?
Top comments (0)