Skip to main content

Command Palette

Search for a command to run...

How to fix maximum update depth exceeded error in React

Updated
4 min read
How to fix maximum update depth exceeded error in React
T

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

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

  1. Never update state directly in component body

  2. Always include dependency arrays in useEffect

  3. Be careful with multiple useEffect hooks that update different state

  4. Use functional updates when new state depends on old state:

     // Instead of:
     setCount(count + 1);
    
     // Use:
     setCount((prevCount) => prevCount + 1);
    
  5. For complex state interactions, consider using a state machine pattern or libraries like XState

  6. 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.

M

pls stop with that cringe anime shit and k y s you stupid n1gger

1