How to fix maximum update depth exceeded error in React

Just a guy who loves to write code and watch anime.
Search for a command to run...

Just a guy who loves to write code and watch anime.
Qualities that make outstanding builders.

Bipedal vs quadrupedal: how many legs This is about the number of legs the creature walks on. Bipedal. Two legs. Humans, ostriches, T-rex, kangaroos, most fantasy humanoids. Quadrupedal. Four legs. Do

Intro You hear "lerp" and "smoothstep" everywhere in game dev. They sound like math jargon. They're not. Both are small tools that do the same job: smoothly move from one value to another. The problem

What is Kinematics Kinematics is the math field. It's the study of how things move without worrying about forces (which would be dynamics). FK and IK are the two branches: forward kinematics and inver

Intro Textures are usually the biggest cost in a 3D scene. Memory, bandwidth, and load time all get eaten by them. Resizing them is the obvious lever. There's more. This post is about the less obvious

This error occurs when React detects too many state updates happening one after another. React has a limit (around 50) of how many render updates can happen in a row. When this limit is exceeded, React stops with this error to prevent your browser from freezing.
function Counter() {
const [count, setCount] = useState(0);
// This runs on every render, creating an infinite loop
setCount(count + 1);
return <div>{count}</div>;
}
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then((data) => setUser(data));
}); // No dependency array = runs after every render
}
function Dashboard() {
const [filters, setFilters] = useState({});
const [results, setResults] = useState([]);
// Update results when filters change
useEffect(() => {
fetchResults(filters).then((data) => setResults(data));
}, [filters]);
// But also update filters when results change
useEffect(() => {
if (results.length === 0) {
setFilters({ ...filters, showEmpty: true });
}
}, [results]); // Creates a cycle: filters → results → filters
}
function ProblemComponent() {
// Add this to suspect components
const renderCount = useRef(0);
renderCount.current++;
console.log(`Component rendered ${renderCount.current} times`);
// Rest of component...
}
useEffect(() => {
console.log("Effect running with filters:", filters);
fetchResults(filters).then((data) => {
console.log("Setting results from fetch");
setResults(data);
});
}, [filters]);
// Understand what led to this setState
// Helpful if you don't know what's causing this setter to be called
console.log(new Error().stack);
setResults(data);
Temporarily disable suspected useEffect hooks to isolate which one is causing the problem (binary elimination).
Move the state update to an event handler or useEffect:
function Counter() {
const [count, setCount] = useState(0);
// Only run once after initial render
useEffect(() => {
setCount(count + 1);
}, []);
return <div>{count}</div>;
}
Add the appropriate dependencies:
useEffect(() => {
fetchUser(userId).then((data) => setUser(data));
}, [userId]); // Only runs when userId changes
const [state, setState] = useState({
filters: {},
results: [],
});
// Update atomically
const updateFilters = (newFilters) => {
setState((prev) => ({
...prev,
filters: newFilters,
}));
};
const initialState = { filters: {}, results: [] };
function reducer(state, action) {
switch (action.type) {
case "SET_FILTERS":
return { ...state, filters: action.payload };
case "SET_RESULTS":
return { ...state, results: action.payload };
case "HANDLE_EMPTY_RESULTS":
// Logic for handling empty results without circular updates
return state.results.length === 0
? { ...state, filters: { ...state.filters, showEmpty: true } }
: state;
default:
return state;
}
}
function Dashboard() {
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
fetchResults(state.filters).then((data) => {
dispatch({ type: "SET_RESULTS", payload: data });
// Let the reducer handle the empty check
dispatch({ type: "HANDLE_EMPTY_RESULTS" });
});
}, [state.filters]);
}
function Dashboard() {
const [filters, setFilters] = useState({});
const [results, setResults] = useState([]);
const prevResultsRef = useRef([]);
useEffect(() => {
fetchResults(filters).then((data) => setResults(data));
}, [filters]);
useEffect(() => {
// Only update filters if this is the first time we've had empty results
if (results.length === 0 && prevResultsRef.current.length > 0) {
setFilters({ ...filters, showEmpty: true });
}
prevResultsRef.current = results;
}, [results]);
}
Never update state directly in component body
Always include dependency arrays in useEffect
Be careful with multiple useEffect hooks that update different state
Use functional updates when new state depends on old state:
// Instead of:
setCount(count + 1);
// Use:
setCount((prevCount) => prevCount + 1);
For complex state interactions, consider using a state machine pattern or libraries like XState
Add comments on useEffect hooks explaining their purpose and expected behavior
The key to fixing this error is understanding the flow of state updates in your app and breaking any circular dependencies.