🌐 Detecting your location…
📢 Advertisement — Configure AdSense in Appearance → Customize → AdSense Settings

How to Fix Maximum Update Depth Exceeded Error in React

⏱️4 min read  ·  847 words

The error Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate (or in a useEffect) means your component is stuck in an infinite render loop. Here’s every cause and its fix.

Why This Happens

React re-renders when state changes. If a state update triggers a render that triggers another state update, you get an infinite loop. React detects this and throws “Maximum update depth exceeded” to prevent your browser from freezing.

Cause 1: Calling setState Directly in Render/Body

// 🐛 setState called during render → triggers re-render → loops forever
function Counter() {
  const [count, setCount] = useState(0);

  setCount(count + 1);  // ❌ runs on every render → infinite loop

  return <div>{count}</div>;
}

// ✅ Only update state in event handlers or effects
function Counter() {
  const [count, setCount] = useState(0);

  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

Cause 2: Calling a Function Instead of Passing It

// 🐛 onClick={handleClick()} CALLS handleClick during render
<button onClick={handleClick()}>Click</button>
// If handleClick calls setState, it loops on every render

// ✅ Pass the function reference, don't call it
<button onClick={handleClick}>Click</button>

// ✅ Or wrap in an arrow function if you need to pass arguments
<button onClick={() => handleClick(id)}>Click</button>

Cause 3: useEffect Updating Its Own Dependency

// 🐛 Effect sets state that's in its dependency array
function Component() {
  const [data, setData] = useState([]);

  useEffect(() => {
    setData([...data, newItem]);  // ❌ changes data → effect re-runs → loop
  }, [data]);  // depends on data, which the effect changes
}

// ✅ Use a functional update and remove data from deps
function Component() {
  const [data, setData] = useState([]);

  useEffect(() => {
    setData(prev => [...prev, newItem]);  // functional update
  }, []);  // runs once — no dependency on data
}

Cause 4: Object/Array/Function in useEffect Dependencies

// 🐛 New object reference every render → effect always re-runs
function Component({ userId }) {
  const options = { id: userId };  // NEW object each render

  useEffect(() => {
    fetchData(options);
    setState(...);
  }, [options]);  // options is "new" every render → infinite loop
}

// ✅ Depend on the primitive value, or memoize the object
function Component({ userId }) {
  useEffect(() => {
    fetchData({ id: userId });
    setState(...);
  }, [userId]);  // primitive — stable across renders

  // OR memoize:
  const options = useMemo(() => ({ id: userId }), [userId]);
}

Cause 5: Setting State in an Event Handler That Fires on Render

// 🐛 onChange fires and setState causes re-render, which re-triggers...
// often from passing changing props to controlled components incorrectly

// 🐛 Also common: updating parent state that re-renders and resets child
function Parent() {
  const [value, setValue] = useState('');
  return <Child onValueChange={setValue(newValue)} />;  // ❌ called in render
}

// ✅ Pass the setter reference
function Parent() {
  const [value, setValue] = useState('');
  return <Child onValueChange={setValue} />;  // pass reference
}

How to Debug the Loop

// 1. Add a log to see how often the component renders
function Component() {
  console.log('render');  // if this floods the console, you have a loop
  // ...
}

// 2. Log inside the effect to find the culprit
useEffect(() => {
  console.log('effect running');
  // ...
}, [dep1, dep2]);

// 3. Log each dependency to see which one keeps changing
useEffect(() => {
  console.log('dep1:', dep1, 'dep2:', dep2);
}, [dep1, dep2]);

// 4. The React DevTools Profiler highlights components re-rendering rapidly

The Golden Rules

  1. Never call setState directly in the component body — only in event handlers, effects, or callbacks
  2. Pass function references to event handlers, don’t call them (onClick={fn} not onClick={fn()})
  3. Use functional updates (setState(prev => ...)) when new state depends on old state
  4. Depend on primitives in useEffect, or memoize objects/functions with useMemo/useCallback
  5. Don’t put state in a useEffect’s deps if the effect updates that state

Frequently Asked Questions

Q: What’s the difference between this and “Too many re-renders”?
A: They’re closely related — both are infinite render loops. “Too many re-renders” usually means setState is called directly during render; “Maximum update depth exceeded” often points to a useEffect or lifecycle method causing the loop. The fixes overlap.

Q: Why does passing setState directly cause a loop?
A: If you write onClick={setState(x)}, that CALLS setState during render (not on click). setState triggers a re-render, which calls it again — infinite loop. Pass onClick={() => setState(x)} instead.

Q: How do I update state based on the previous state safely?
A: Use a functional update: setCount(prev => prev + 1). This doesn’t require count in your dependency array, avoiding a common loop cause.

Q: My object dependency keeps causing loops. What do I do?
A: Objects and arrays get new references every render. Either depend on primitive values inside them ([obj.id] not [obj]), or memoize the object with useMemo(() => obj, [deps]).

Q: Can ESLint catch these?
A: The eslint-plugin-react-hooks exhaustive-deps rule flags many dependency issues. It won’t catch every loop, but it prevents the most common useEffect dependency mistakes. Install and enable it.

Conclusion

“Maximum update depth exceeded” is always an infinite render loop. The most common causes: calling setState directly in the render body, calling (instead of passing) a function in an event handler, a useEffect that updates its own dependency, or unstable object/function dependencies. The fixes: only update state in handlers and effects, pass function references, use functional updates, and depend on primitives or memoized values. Add a console.log('render') to confirm the loop, then log each dependency to find which one keeps changing — the culprit is usually obvious once you see it.

✍️ Leave a Comment

Your email address will not be published. Required fields are marked *

🌐 Read in:🇬🇧 English🇩🇪 Deutsch🇧🇷 Português🇸🇦 العربية🇮🇳 हिन्दी🇧🇩 বাংলা