HuntExams Academy logo
ReactAdvanced#effects#debugging

What causes an infinite re-render loop?

Answer

Setting state unconditionally during render or in an effect whose dependency changes on every render — for example a new object or array literal in the dependency array.

Example

// loop
useEffect(() => setCount(count + 1));
// loop: new object each render
useEffect(() => load(), [{ page }]);
// fix
useEffect(() => load(), [page]);

Detailed explanation

An infinite re-render loop occurs when a component repeatedly schedules an update that triggers another render, without reaching a stable state. The central issue is a feedback cycle: rendering or an effect updates state, and that update causes the same logic to run again. React may report “Too many re-renders” for render-phase updates or “Maximum update depth exceeded” for repeated update cycles. In interviews, explain the feedback cycle rather than simply saying that useEffect causes loops.

One common cause is calling a state setter unconditionally inside the component body. Rendering should calculate JSX from props and state, not repeatedly schedule updates. For example, calling setCount(count + 1) during render immediately requests another render, which executes the setter again. Accidentally writing onClick={setCount(count + 1)} has the same problem: the setter runs while React evaluates JSX, rather than when the user clicks. Pass a function instead: onClick={() => setCount(c => c + 1)}.

Another cause is an effect that updates state and then runs again because of that update. useEffect(() => setCount(count + 1)) has no dependency array, so it runs after every committed render and increments the counter again. Adding [count] does not fix this particular cycle, because the effect itself changes count. React compares each dependency with its previous value using Object.is. Consequently, [{ page }] contains a fresh object on every render, even when page is unchanged. If load() updates state so that the component re-renders, useEffect(() => load(), [{ page }]) can repeat indefinitely. An unstable dependency alone does not create a loop; it must participate in an update cycle.

Fix the cause rather than suppressing dependency warnings. Derive values during render when possible, put interaction-driven updates in event handlers, and use effects to synchronise with external systems. Depend on the actual reactive inputs, such as page, and construct request objects inside the effect. The shorthand fix [page] is sufficient only if load does not introduce another reactive dependency; otherwise restructure or stabilise it appropriately. Development Strict Mode can run an extra effect setup-and-cleanup cycle, but that finite diagnostic behaviour is not an infinite loop.

Key points to remember

  • A loop requires a repeating update → render → update feedback cycle.
  • Unconditional state updates during render are a common direct cause.
  • An effect without a dependency array runs after every committed render.
  • React compares dependencies using Object.is; fresh objects, arrays, and functions have different identities.
  • Fix dependency design and update placement rather than hiding dependency warnings.

Common mistakes candidates make

  • Assuming a new object in an effect's dependencies causes a loop even when the effect does not trigger an update.
  • Adding [count] to an effect that always increments count and expecting the cycle to stop.
  • Using an empty dependency array indiscriminately, leaving stale values or missing required synchronisation.

Likely follow-up questions

Does a functional state updater prevent infinite loops?

Not by itself: setCount(c => c + 1) still changes state every time it runs. It avoids relying on a captured count value, but an effect that executes it after every render will still loop.

When should you use useMemo or useCallback for effect dependencies?

Use them when a necessary object or function dependency needs a stable identity while its inputs remain unchanged. First consider moving its construction inside the effect or depending directly on primitive inputs; memoisation cannot fix a genuine logical cycle.

How do you distinguish Strict Mode behaviour from an actual loop?

In development, Strict Mode can perform an extra effect setup-and-cleanup cycle to expose missing cleanup and other bugs. A genuine loop keeps scheduling updates rather than stopping after a finite diagnostic sequence.

Interview tip: Trace one complete update-and-render cycle, then show the smallest fix that preserves the intended behaviour.

Related Interview Questions

1
ReactIntermediate#state#patterns

What is lifting state up?

Open
2
ReactIntermediate#patterns#context

What is prop drilling and how do you avoid it?

Open
3
ReactBeginner#patterns

What is the children prop and composition?

Open