HuntExams Academy logo
ReactIntermediate#hooks#effects

Explain useEffect and its dependency array.

Answer

useEffect runs side effects after render. With no array it runs after every render, with [] only after mount, and with dependencies whenever one of them changes. The returned function cleans up before the next run and on unmount.

Example

useEffect(() => {
  const id = setInterval(tick, 1000);
  return () => clearInterval(id);
}, []);

Detailed explanation

useEffect is a React Hook that synchronises a component with systems outside React, such as browser events, timers, network connections, or third-party widgets. Rendering should remain pure: it calculates JSX without starting subscriptions or changing external state. An Effect runs after React commits an update, not during render, so abandoned renders do not execute it. It generally runs after the browser paints, although interaction-related Effects may run before paint. Interviewers expect this distinction rather than just “it runs after render.”

The dependency array tells React which reactive values the Effect uses. These include props, state, and variables or functions declared inside the component. Without an array, the Effect runs after every commit of that component. With [], it has no reactive dependencies and normally runs once per mount, not once for the entire application. With [userId], it runs on mount and after commits where userId differs from its previous value. React compares each dependency using Object.is, so newly created objects and functions count as changed even when their contents or behaviour seem identical.

An Effect can return a cleanup function. Before rerunning an Effect because dependencies changed, React calls its previous cleanup, then executes the new setup; cleanup also runs when the component unmounts. In the timer example, setup creates an interval and cleanup clears that interval, preventing it from continuing after unmount. However, [] is correct only if tick does not depend on changing reactive values. If tick is declared inside the component or captures props or state, omitting dependencies can produce a stale closure: the callback keeps reading values from an earlier render.

In development Strict Mode, React performs an extra setup–cleanup–setup cycle to reveal missing cleanup and unsafe assumptions; this is not normal production behaviour. Use the exhaustive-deps lint rule instead of suppressing dependencies to control execution. Restructure code, use functional state updates where appropriate, or stabilise references when necessary. Also recognise when no Effect is needed: calculate derived values during render and handle user-triggered actions in event handlers. Effects run only on the client, not during server rendering.

Key points to remember

  • useEffect synchronises committed component state with external systems; it does not run during render.
  • No array means every commit; [] means per mount; [deps] means mount plus commits with changed dependencies.
  • Include every reactive value read by the Effect; React compares dependencies using Object.is.
  • Cleanup reverses setup before dependency-driven reruns and on unmount.
  • Development Strict Mode adds a setup–cleanup–setup check, so Effects must tolerate restarting.

Common mistakes candidates make

  • Using [] while reading changing props or state, causing stale closures.
  • Forgetting to clear timers, remove listeners, or disconnect subscriptions in cleanup.
  • Creating an update loop by setting state in an Effect whose dependencies change as a result.

Likely follow-up questions

How would you safely increment a counter inside an interval?

Define the callback inside the Effect and use setCount(previous => previous + 1), returning cleanup that clears the interval. If it reads no other reactive values, [] is appropriate because the state setter is stable and the functional updater receives the latest state.

How is useLayoutEffect different from useEffect?

useLayoutEffect runs after DOM updates but before the browser repaints, and its work blocks painting. Use it for layout measurements or visual corrections that must happen before paint; prefer useEffect for ordinary synchronisation.

How do you avoid stale responses when fetching data in an Effect?

Include the request inputs in the dependency array and use cleanup to abort the previous request or mark its result as obsolete. Guard state updates so an older response cannot overwrite newer data, and define an inner async function rather than making the Effect callback itself async.

Interview tip: Explain setup, dependency comparison, and cleanup in that order, then use the interval example to discuss stale closures and Strict Mode.

Related Interview Questions

1
ReactIntermediate#hooks

What are the rules of hooks?

Open
2
ReactIntermediate#hooks#refs

What is useRef used for?

Open
3
ReactAdvanced#hooks#performance

Difference between useMemo and useCallback?

Open