React Error #185: Maximum update depth exceeded β Fixed
What triggers this error?
React limits component re-renders to prevent the browser tab from hanging. When a component re-renders, triggers a state update, which causes another re-render, which triggers another state update β React detects the cycle after a threshold (around 50 renders) and throws Error #185: "Maximum update depth exceeded".
There are two main patterns that cause this: calling setState directly in the render body, and a useEffect with a dependency that it mutates.
Cause 1: Calling setState directly in the render body
Calling a state setter directly in JSX β not inside an event handler β runs it on every render, scheduling another render immediately.
β Causes the error β setState called during render
// β BAD: setCount(count + 1) runs on every render β infinite loop
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
setCount(count + 1); // called directly in the render body!
return <p>Count: {count}</p>;
}
β Correct β wrap in an event handler
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
// state update is ONLY triggered by user interaction
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Cause 2: Calling a function instead of passing it in onClick
A subtle variant: calling the function immediately instead of passing a reference. onClick={handleClick()} runs handleClick during render, not on click. If handleClick calls setState, you get a loop.
β Causes the error β function invoked at render time
// β BAD: handleClick() executes during render, not on user click
function handleClick(setValue: React.Dispatch<React.SetStateAction<number>>) {
setValue(prev => prev + 1);
}
export default function App() {
const [value, setValue] = useState(0);
return <button onClick={handleClick(setValue)}>Click me</button>;
// ^^^^^^^^^^^^^^^^^ called immediately!
}
β Correct β pass a reference or wrap in arrow function
export default function App() {
const [value, setValue] = useState(0);
// Option A: arrow function wrapper
return <button onClick={() => handleClick(setValue)}>{value}</button>;
// Option B: bind partially β same result
// return <button onClick={handleClick.bind(null, setValue)}>{value}</button>;
}
Cause 3: useEffect mutates a value in its own dependency array
If useEffect sets state that is also listed as a dependency, every state change triggers the effect, which triggers another state change β infinite loop.
β Causes the error β effect updates its own dependency
import { useState, useEffect } from 'react';
export default function AutoTimer() {
const [time, setTime] = useState(Date.now());
useEffect(() => {
setTime(Date.now()); // updates `time`β¦
}, [time]); // β¦which is a dependency β runs again forever
// ^^^^^ INFINITE LOOP
return <p>{time}</p>;
}
β Correct β remove the variable from dependencies or use a ref
import { useState, useEffect } from 'react';
export default function AutoTimer() {
const [time, setTime] = useState(Date.now());
useEffect(() => {
// Run ONCE on mount (empty array = no reactive dependencies)
const id = setInterval(() => setTime(Date.now()), 1000);
return () => clearInterval(id); // cleanup on unmount
}, []); // β
empty deps β effect runs once
return <p>{new Date(time).toLocaleTimeString()}</p>;
}
Cause 4: Object or array literal as a useEffect dependency
Object and array literals are recreated on every render. React compares dependencies by reference, so {} !== {} β the effect runs every time.
β Causes the error β inline object in deps
useEffect(() => {
fetchUsers(filters);
setLoading(false);
}, [{ page: 1, limit: 10 }]); // new object on every render β always "changed"
β Correct β destructure primitives or use useMemo
// Option A: depend on primitive values, not the whole object
const { page, limit } = filters;
useEffect(() => {
fetchUsers({ page, limit });
setLoading(false);
}, [page, limit]); // stable primitives β only re-runs when values actually change
// Option B: memoize the object if you must pass it whole
const stableFilters = useMemo(() => ({ page: 1, limit: 10 }), []);
useEffect(() => {
fetchUsers(stableFilters);
}, [stableFilters]); // reference is stable now
Frequently Asked Questions
How do I find which component is causing the loop?
Open the React DevTools Profiler tab and record a few seconds. The component that appears hundreds of times in the flame chart is your culprit. Alternatively, add a console.trace() call inside the state setter function (using the functional update form: setState(prev => { console.trace(); return prev + 1; })) to see the exact call stack that triggers it.
The error only appears after adding a third-party library. What now?
Some libraries (state managers, motion libraries, form hooks) call setState internally during render if their API is misused. Check that you are calling the hook's setter in an event handler or useEffect, not at the top level of your component. Also verify the library version β this is a common bug that gets patched in minor releases.
Can useReducer prevent this error?
No β the same rules apply. A reducer dispatched during render or from an effect that depends on the state it updates will produce the same infinite loop. useReducer just gives you a cleaner action/state model; the dependency management rules are identical to useState.