How to fix maximum update depth exceeded error in React

Just a guy who loves to write code and watch anime.
What the error means
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.
Common causes
1. Setting state in component body
function Counter() {
const [count, setCount] = useState(0);
// This runs on every render, creating an infinite loop
setCount(count + 1);
return <div>{count}</div>;
}
2. Missing dependency array in useEffect
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then((data) => setUser(data));
}); // No dependency array = runs after every render
}
3. Circular state dependencies (most common)
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
}
How to debug this error
Step 1: Add render counting
function ProblemComponent() {
// Add this to suspect components
const renderCount = useRef(0);
renderCount.current++;
console.log(`Component rendered ${renderCount.current} times`);
// Rest of component...
}
Step 2: Track state changes with named console logs
useEffect(() => {
console.log("Effect running with filters:", filters);
fetchResults(filters).then((data) => {
console.log("Setting results from fetch");
setResults(data);
});
}, [filters]);
Step 3: Use the stack trace to find the source
// 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);
Step 4: Comment out effects one by one
Temporarily disable suspected useEffect hooks to isolate which one is causing the problem (binary elimination).
Solutions for different scenarios
For simple state updates in component body
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>;
}
For missing dependency arrays
Add the appropriate dependencies:
useEffect(() => {
fetchUser(userId).then((data) => setUser(data));
}, [userId]); // Only runs when userId changes
For circular dependencies
1. Combine related state
const [state, setState] = useState({
filters: {},
results: [],
});
// Update atomically
const updateFilters = (newFilters) => {
setState((prev) => ({
...prev,
filters: newFilters,
}));
};
2. Use useReducer for complex state
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]);
}
3. Use refs to break the cycle
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]);
}
Prevention best practices
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.






