Fixing 'Maximum update depth exceeded' in React: Stop the Infinite Loop

intermediateβš›οΈ React2026-06-06| React 16.8+, Web Browsers (Chrome, Edge, Firefox), Vite, Create React App, Next.js

Error Message

Error: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
#react#useEffect#setState#maximum-update-depth#infinite-loop

The Emergency Brake

React just pulled the emergency brake. This error usually means your component is caught in a loop, re-rendering so fast it would freeze the browser if React didn't stop it. You'll see a wall of red text in your console, and the UI will likely stop responding.

Error: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect...

TL;DR Quick Fixes

  • Missing the Array: If the effect should only run once when the component mounts, add [] as the second argument.
  • State Feedback Loops: Updating state that is also a dependency? Switch to a functional update: setCount(c => c + 1) instead of setCount(count + 1).
  • Unstable References: If your dependency is an object or array defined inside your component, it's a "new" object every render. Wrap it in useMemo.
  • The Guard Clause: Wrap your setState in an if statement to ensure it only fires when the data actually changes.

Why This Happens

Think of this error as a safety net. React monitors your render cycles and intervenes if it detects more than 50 consecutive updates in a single second. In useEffect, this is almost always a circular chain reaction: Render β†’ Effect β†’ State Change β†’ Re-render β†’ Effect.

1. The Missing Dependency Array

Forgetting the second argument is a common slip-up. Without it, the effect runs after every single render. If that effect updates state, it forces a new render, which runs the effect again. It’s a never-ending cycle.

// ❌ BAD: Infinite loop
useEffect(() => {
  setUserData(data);
}); // No array here!

2. The Referential Equality Trap

JavaScript compares objects and arrays by reference, not by value. In plain English: [] === [] is false. If you define a constant object inside your component and pass it to a dependency array, React thinks it's a brand new dependency every time the component renders.

// ❌ BAD: 'options' is a new memory reference every render
const options = { theme: 'dark' };

useEffect(() => {
  // Logic here
}, [options]);

How to Fix It

Approach 1: Use Functional Updates

Stop passing the current state into the dependency array just to update it. By using a functional update, you can remove the state variable from the dependency list entirely, breaking the loop.

// ❌ BAD: 'count' in the array causes a loop
useEffect(() => {
  const timer = setInterval(() => setCount(count + 1), 1000);
  return () => clearInterval(timer);
}, [count]);

// βœ… GOOD: No 'count' dependency needed
useEffect(() => {
  const timer = setInterval(() => setCount(prev => prev + 1), 1000);
  return () => clearInterval(timer);
}, []); 

Approach 2: Stabilize Objects with useMemo

If an object must be a dependency, ensure its reference stays the same across renders. useMemo caches the object so React only sees it as "changed" when its internal values actually shift.

// βœ… GOOD: 'options' only changes if 'theme' changes
const options = useMemo(() => ({
  color: theme === 'dark' ? '#fff' : '#000'
}), [theme]);

useEffect(() => {
  // Logic runs only when the specific theme changes
}, [options]);

Approach 3: The Manual Comparison

Before you trigger a state update, check if you actually need to. This simple guard clause prevents unnecessary re-renders from snowballing into a crash.

useEffect(() => {
  if (newId !== currentId) {
    setCurrentId(newId);
  }
}, [newId, currentId]);

How to Verify the Fix

  • Watch the Console: Refresh the page. The red error block should vanish.
  • Check the Network Tab: If your effect fetches data, ensure you don't see dozens of identical requests firing per second. One or two is normal; fifty is a bug.
  • React Profiler: Use the Profiler in React DevTools. If the "Commit" timeline shows a flat line of rapid updates for one component, the loop is still there.

Further Reading

Related Error Notes