The 2 AM Production Pager
Itâs late, and your error monitoring tool just spiked. Users are reporting that the dashboard is frozen or, worse, completely blank. You open the production console, hoping for a clear stack trace, but all you get is a frustratingly vague link:
Error: Minified React error #185; visit https://reactjs.org/docs/error-decoder.html?invariant=185 for the full message.
React strips out descriptive error messages in production to save precious kilobytes. If you follow that link, the decoder reveals the truth: "Maximum update depth exceeded." This means your app is stuck in an infinite loop. React has a safety mechanism that kills the component tree after it hits a limitâusually 50 nested updatesâto keep the browser from catching fire.
Why the Loop Happens
At its heart, the problem is a logic feedback loop. One render triggers a state change, which forces another render, which triggers the same state change again. It happens fastâoften hitting that 50-render ceiling in a fraction of a second.
Here is what usually trips developers up:
- Direct State Injection: Calling
setStatedirectly in the body of a functional component. - The Dependency Trap: A
useEffecthook that updates a piece of state it is also watching. - Immediate Execution: Passing
onClick={handleClick()}instead ofonClick={handleClick}. This executes the function during the render phase rather than waiting for the click. - Prop-State Synchronization: Using
getDerivedStateFromPropsor similar logic in class components that triggers updates on every cycle.
The Quick Fix: Finding the Needle
Debugging minified code is a nightmare. If you have Source Maps uploaded to a private Sentry or Datadog instance, use them. They will map that cryptic at abc (main.js:10) back to the actual line in your UserProfile.tsx.
Without source maps, you have to play detective with the stack trace. Look for repeating patterns in the minified function names like Xy, Za, or Xy again. This repetition usually points to the component that is looping. Check your most recent git commitsâspecifically any changes involving data fetching or complex useEffect logic.
The Permanent Fix: Breaking the Cycle
1. Clean Up useEffect Dependencies
Most #185 errors stem from hooks. Consider this common mistake:
useEffect(() => {
setCount(count + 1);
}, [count]); // This is an instant infinite loop.
Instead of watching the variable you are changing, use a functional update. This removes the need to include the variable in the dependency array:
useEffect(() => {
// Only run once on mount
setCount(prev => prev + 1);
}, []);
2. Stop Executing Handlers on Render
Check your JSX for stray parentheses. If you see <button onClick={doSomething()}>, youâve found your culprit. That function runs the moment the button renders. If doSomething updates state, the component re-renders, runs the function again, and crashes the app. Always pass the reference: <button onClick={doSomething}>.
3. Move to Derived State
Stop using useEffect to sync two pieces of state. Itâs inefficient and dangerous. If you are filtering a list based on a search term, don't store the filteredList in state. Use useMemo instead:
// Instead of triggering a second render with useEffect
const filteredData = useMemo(() => {
return data.filter(item => item.active);
}, [data]);
This calculates the value during the initial render, saving you an entire lifecycle step and eliminating the risk of a loop.
Verification Steps
Don't just push to production and hope for the best. Verify the fix in a staging environment first:
- The Profiler Test: Open React DevTools and use the "Profiler" tab. Record a few seconds of activity. If you see a single component committing dozens of times per second, the loop is still there.
- CPU Monitoring: Watch your browser's Task Manager. If the CPU usage for your tab jumps to 100% and stays there when you navigate to a specific page, your logic is still spiraling.
Pro-Tips for Debugging
Sometimes the loop is triggered by weird data in the URL, like a malformed Base64 string or an unexpected query parameter. When Iâm stuck at 2 AM trying to figure out why a URL-driven state is crashing, I use the URL Encoder/Decoder on ToolCraft. It helps me quickly parse and inspect production URL snippets to see if a hidden character is causing my parsing logic to fail and trigger an update loop.
Finally, let your tools do the heavy lifting. Ensure eslint-plugin-react-hooks is set to "error" in your CI/CD pipeline. It catches almost every dependency-related loop before the code ever leaves your laptop.

