It Started With a Frozen Screen...
Everything looked fine.
My React application compiled successfully, there were no syntax errors, and the UI loaded exactly as expected.Then I clicked a button.A few seconds later, the page became sluggish.
The CPU usage shot up.
The browser started lagging.
And then...
React crashed with:
Maximum update depth exceeded.
At first, I assumed it was a complex bug somewhere in my API or state management.
I couldn't have been more wrong.
The Investigation
I opened the browser's Developer Tools.
The Network tab looked suspicious.
The same API request was firing repeatedly.
Not twice.
Not ten times.
Hundreds of times.
Every response triggered another request.
Something inside my component kept telling React to render again... and again... and again.
The Culprit
After tracing the component, I found this innocent-looking code:
useEffect(() => {
fetchUsers();
setUsers(data);
}, [users]);
At first glance, it didn't seem wrong.
But here's what was happening:
The component rendered.
-useEffect executed.
-setUsers() updated the state.
-Updating the state triggered another render.
Since users changed, useEffect ran again.
Repeat forever.
It was an infinite rendering loop.
The Fix
The solution wasn't complicated.
I changed the dependency array so the effect only ran when it actually needed to.
useEffect(() => {
fetchUsers();
}, []);
In another part of the component, I also memoized values that were being recreated on every render using useMemo and wrapped callback functions with useCallback where appropriate.
These small changes completely eliminated the unnecessary renders.
Before vs After
-Before
Browser became unresponsive.
Hundreds of API calls.
High CPU usage.
React threw "Maximum update depth exceeded."
Poor user experience.
-After
One API request.
Stable rendering.
Smooth navigation.
Lower CPU usage.
Cleaner component logic.
Sometimes the smallest dependency array causes the biggest headaches.
What I Learned
This bug taught me several valuable lessons.
Don't update the same state that your effect depends on unless you truly intend to trigger another effect.
Always review your dependency arrays carefully.
Remember that every state update causes another render.
If an effect updates a dependency it listens to, ask yourself:
"Will this ever stop?"
That single question can save hours of debugging.
If Sentry Were Watching...
This issue would have been much easier to investigate with Sentry.
Features like:
Error Monitoring
Performance Monitoring
Session Replay
could quickly reveal excessive renders, repeated network requests, and performance bottlenecks, making the root cause much easier to identify.
Final Thoughts
This wasn't the most complicated bug I've ever encountered.
It wasn't caused by an obscure library or a hidden browser issue.
It came from a single useEffect dependency.
That's what makes React both powerful and challenging.
A tiny mistake can create an infinite rendering loop—but once you understand how React thinks, these bugs become much easier to spot.
Now, whenever I write a new useEffect, I pause for a moment and ask:
"Could this accidentally trigger itself?"
That habit has already saved me from repeating the same mistake.
Thanks for reading, and happy debugging!
Top comments (0)