React Error 185 Infinite Loop

React Error #185: Maximum update depth exceeded β€” Fixed

React Error #185: Maximum update depth exceeded β€” infinite render loop

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.