How to fix Maximum update depth exceeded in React

ReactINTERMEDIATECRITICALUpdated Dec 8, 2025· 224 views

This React error occurs when a component triggers too many consecutive state updates, usually from calling setState in useEffect without proper dependencies, or in event handlers without guards. React limits nested updates to around 50 to prevent infinite loops that would freeze the browser.

Quick fix

Fixes Maximum update depth exceeded

Error: Maximum update depth exceeded. This can happen when a component
repeatedly calls setState inside componentWillUpdate or componentDidUpdate.
React limits the number of nested updates to prevent infinite loops.

  at ComponentName (ComponentName.tsx:45)
  1. Identify the component and state update causing the loop
  2. Fix setState calls directly in component body
  3. Fix useEffect infinite loops with proper dependencies
  4. Fix event handler function calls
react — error
Error: Maximum update depth exceeded. This can happen when a component
repeatedly calls setState inside componentWillUpdate or componentDidUpdate.
React limits the number of nested updates to prevent infinite loops.
at ComponentName (ComponentName.tsx:45)
Maximum update depth exceeded

What this error means

This error is React's safety mechanism to prevent infinite render loops from crashing your browser. When React detects more than approximately 50 consecutive state updates happening one after another, it throws this error and stops the update cycle. The root cause is typically a state update that triggers a re-render, which then triggers another state update, creating a cycle that never ends. This commonly happens when setState is called directly in the component body, in useEffect without proper dependencies, or in lifecycle methods like componentDidUpdate without conditional checks. React enforces this limit because without it, an infinite loop would consume all available memory and freeze the browser tab. Unlike many React errors that are warnings, this one completely halts your component from rendering because continuing would be harmful. The error message usually indicates which component is causing the loop, though in complex applications it may take some debugging to identify the exact state update creating the cycle.

Typical symptoms

  • Error message: "Maximum update depth exceeded" in browser console
  • Component stops rendering entirely and shows error boundary or blank screen
  • Browser tab becomes unresponsive or slow before error appears
  • Error occurs immediately on component mount or after specific user interactions
  • React DevTools Profiler shows excessive re-renders before crash
  • Component flashes or updates rapidly before freezing
  • Error stack trace points to useEffect, componentDidUpdate, or event handlers

Common causes

  1. Calling setState or state updater functions directly in component body during render
  2. useEffect with missing or incorrect dependency array that updates a dependency it watches
  3. Calling setState inside componentDidUpdate or componentWillUpdate without conditional guards
  4. Event handler callbacks being invoked immediately during render instead of on user action
  5. State updates triggered by derived state calculations that depend on the state being updated
  6. Objects or functions in useEffect dependency arrays that are recreated on every render
  7. Parent-child state updates that trigger each other in a cycle
  8. Too frequent state updates in rapid succession from event listeners without throttling or debouncing

How to fix "Maximum update depth exceeded"

1Identify the component and state update causing the loop

Check the error stack trace in your browser console to identify which component is causing the error:

bash
Error: Maximum update depth exceeded. This can happen when a component
repeatedly calls setState inside componentWillUpdate or componentDidUpdate.
React limits the number of nested updates to prevent infinite loops.

  at ComponentName (ComponentName.tsx:45)

Use React DevTools Profiler to record the interaction that triggers the error. Look for components with many consecutive re-renders. Examine any setState calls, state updater functions from useState, or dispatch calls in the problematic component.

2Fix setState calls directly in component body

Never call state setters in the component render body. Move them to useEffect or event handlers:

jsx
// ❌ Bad: Calling setState during render
function Counter() {
  const [count, setCount] = useState(0);
  setCount(count + 1); // Infinite loop!
  return <div>{count}</div>;
}

// ✅ Good: Use useEffect with empty dependencies
function Counter() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    setCount(count + 1);
  }, []); // Only runs once on mount
  
  return <div>{count}</div>;
}

// ✅ Better: Initialize state with the value
function Counter() {
  const [count] = useState(1); // Start at 1 if that's what you need
  return <div>{count}</div>;
}
3Fix useEffect infinite loops with proper dependencies

Ensure useEffect dependencies don't cause the effect to re-run when it updates those dependencies:

jsx
// ❌ Bad: Updating state that triggers the effect
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [user]); // user changes, effect runs, sets user, repeat!
  
  return <div>{user?.name}</div>;
}

// ✅ Good: Depend only on external values
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]); // Only re-run when userId changes
  
  return <div>{user?.name}</div>;
}

If useEffect must update a state it depends on, use the functional updater form to avoid the dependency.

4Fix event handler function calls

Pass function references to event handlers, don't call them directly:

jsx
// ❌ Bad: Calling function immediately
function Button() {
  const [count, setCount] = useState(0);
  
  return (
    <button onClick={setCount(count + 1)}>
      {/* setCount is called during render! */}
      Click me
    </button>
  );
}

// ✅ Good: Pass function reference
function Button() {
  const [count, setCount] = useState(0);
  
  return (
    <button onClick={() => setCount(count + 1)}>
      Click me: {count}
    </button>
  );
}

// ✅ Better: Use functional update form
function Button() {
  const [count, setCount] = useState(0);
  
  return (
    <button onClick={() => setCount(c => c + 1)}>
      Click me: {count}
    </button>
  );
}
5Fix objects and functions in dependency arrays

Objects and functions recreated on every render cause useEffect to run repeatedly:

jsx
// ❌ Bad: Object created on every render
function Component() {
  const [data, setData] = useState(null);
  const options = { enabled: true }; // New object each render
  
  useEffect(() => {
    fetchData(options).then(setData);
  }, [options]); // options changes every render!
  
  return <div>{data}</div>;
}

// ✅ Good: Use useMemo for objects
function Component() {
  const [data, setData] = useState(null);
  const options = useMemo(() => ({ enabled: true }), []);
  
  useEffect(() => {
    fetchData(options).then(setData);
  }, [options]);
  
  return <div>{data}</div>;
}

// ✅ Better: Use primitive dependencies or move object inside effect
function Component() {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    const options = { enabled: true };
    fetchData(options).then(setData);
  }, []); // No object dependencies needed
  
  return <div>{data}</div>;
}

Use useCallback for function dependencies to prevent recreation.

6Add conditional guards to componentDidUpdate

For class components, always check conditions before calling setState in componentDidUpdate:

jsx
// ❌ Bad: Unconditional setState
class Component extends React.Component {
  componentDidUpdate(prevProps) {
    this.setState({ updated: true }); // Infinite loop!
  }
}

// ✅ Good: Conditional setState
class Component extends React.Component {
  componentDidUpdate(prevProps) {
    if (prevProps.userId !== this.props.userId) {
      this.setState({ loading: true });
      this.fetchUser(this.props.userId);
    }
  }
}

// ✅ Better: Use getDerivedStateFromProps for state derived from props
class Component extends React.Component {
  static getDerivedStateFromProps(props, state) {
    if (props.userId !== state.prevUserId) {
      return { prevUserId: props.userId, loading: true };
    }
    return null;
  }
}
7Add throttling or debouncing for rapid updates

For event handlers that trigger frequent state updates, use throttling or debouncing:

jsx
import { useState, useCallback } from 'react';
import { debounce } from 'lodash';

// ❌ Bad: Updating on every input change can trigger rapid re-renders
function SearchInput() {
  const [value, setValue] = useState('');
  const [results, setResults] = useState([]);
  
  const handleChange = (e) => {
    setValue(e.target.value);
    fetchResults(e.target.value).then(setResults);
  };
  
  return <input onChange={handleChange} />;
}

// ✅ Good: Debounce the search
function SearchInput() {
  const [value, setValue] = useState('');
  const [results, setResults] = useState([]);
  
  const debouncedSearch = useCallback(
    debounce((query) => {
      fetchResults(query).then(setResults);
    }, 300),
    []
  );
  
  const handleChange = (e) => {
    setValue(e.target.value);
    debouncedSearch(e.target.value);
  };
  
  return <input value={value} onChange={handleChange} />;
}

This prevents overwhelming React with too many state updates in quick succession.

Advanced notes

React's Update Limit: React limits nested updates to approximately 50 (the exact number may vary between versions). This threshold balances catching infinite loops while allowing legitimate nested updates like cascading state changes or multiple useEffect chains.

Batching in React 18+: React 18 introduced automatic batching for all updates, including those in promises, setTimeout, and native event handlers. While this reduces re-renders, it doesn't prevent infinite loops - you still need proper dependency management and conditional guards.

Debugging Tools: Use React DevTools Profiler's "Highlight updates" feature to visually see which components are re-rendering excessively. Enable "Record why each component rendered" to see what props or state changes triggered each render, making it easier to trace the loop source.

Testing Considerations: Write unit tests that verify components don't update in loops. Use act() from react-testing-library to catch async state update issues. Monitor test execution time - tests that hang or timeout often indicate infinite update loops.

Performance Monitoring: In production, implement error boundaries with monitoring tools like Sentry to catch these errors before users report them. Track component re-render counts in performance metrics to identify potential issues before they hit the update limit. Consider using React.memo and useMemo strategically to prevent unnecessary re-renders that could contribute to hitting the limit.

Frequently asked questions

What does "Maximum update depth exceeded" mean?
This error is React's safety mechanism to prevent infinite render loops from crashing your browser. When React detects more than approximately 50 consecutive state updates happening one after another, it throws this error and stops the update cycle. The root cause is typically a state update that triggers a re-render, which then triggers another state update, creating a cycle that never ends.
How do I fix "Maximum update depth exceeded"?
This React error occurs when a component triggers too many consecutive state updates, usually from calling setState in useEffect without proper dependencies, or in event handlers without guards. React limits nested updates to around 50 to prevent infinite loops that would freeze the browser.
What causes "Maximum update depth exceeded"?
Calling setState or state updater functions directly in component body during render useEffect with missing or incorrect dependency array that updates a dependency it watches Calling setState inside componentDidUpdate or componentWillUpdate without conditional guards
Is the "Maximum update depth exceeded" error serious?
Yes. This is a critical-severity error that usually blocks work until it is fixed, but the steps above resolve it.

Official resources & further reading