React Curriculum

useEffect Mental Model: Synchronization Over Lifecycle

Medium100% Free~20 mins#react#useeffect#synchronization#dependencies#reactive-values
Interview Answer (30-Second Summary)

useEffect synchronizes your React component with an external system (browser DOM, network, timers, or third-party widgets) outside of React. It runs after the browser paints so it never blocks layout rendering, comparing dependencies using Object.is.

Key Learning Objectives
✓

Shift mental models from "when does this component mount?" to "which external system does this effect synchronize with?".

✓

Understand how React uses Object.is shallow comparison on the dependency array.

✓

Explain why useEffect runs after browser layout and paint, unlike useLayoutEffect.

✓

Identify which values must be included in the dependency array (all reactive values).

The Interview Problem & Context

Explain the "synchronization" mental model of useEffect. Why does the React ESLint plugin insist that every reactive value read inside an effect must be included in the dependency array?

1import { useEffect } from 'react';
2
3interface ChatRoomProps {
4 roomId: string;
5 serverUrl: string;
6}
7
8export function ChatRoom({ roomId, serverUrl }: ChatRoomProps) {
9 useEffect(() => {
10 // 1. Synchronize: Connect to the external chat room system
11 const connection = createConnection({ serverUrl, roomId });
12 connection.connect();
13
14 // 2. Disconnect: Clean up when roomId, serverUrl changes, or unmounts
15 return () => {
16 connection.disconnect();
17 };
18 }, [roomId, serverUrl]); // All reactive values read inside the effect must be listed!
19
20 return <h1>Welcome to room: {roomId}</h1>;
21}
Deep Architectural Explanation

The Synchronization Mental Model

The most common misconception about useEffect is treating it as a lifecycle hook.

  • Lifecycle thinking: "Run this code on mount, run this on update, run this on unmount."
  • Synchronization thinking: "Keep the component synchronized with an external system as long as specific conditions or reactive values hold true."

The Dependency Array Contract

Every value from component scope (props, state, or functions derived from them) that is read inside the effect is a reactive value.

  • React compares each item in the dependency array against its value in the previous render using Object.is(prevDep, nextDep).
  • If any dependency changed, React cleans up the previous effect and re-synchronizes with the new values.
  • If you lie to React by omitting dependencies, your effect's closures will capture stale variables and permanently talk to outdated rooms, IDs, or configurations.
Common Interview Traps & Pitfalls
✕

Suppressing eslint-plugin-react-hooks (eslint-disable-next-line react-hooks/exhaustive-deps) instead of fixing dependencies.

✕

Passing objects or inline functions as dependencies, causing the effect to re-run on every render because new object references fail Object.is checks.

✕

Using useEffect to fetch data on user action (like clicking a submit button) instead of triggering the fetch in the onClick handler.

Senior Interviewer Follow-Up Probes
?

Why does useEffect run asynchronously after browser paint, while useLayoutEffect runs synchronously before paint?

?

How does the useEffectEvent hook proposal allow you to read latest props without adding them to dependencies?

?

When is an effect NOT needed according to React official guidelines?

Frequently Asked Interview Questions
What is an "external system" in the context of useEffect?

Any piece of state or API not managed by React: browser window events, manual DOM animations, WebSockets, setInterval timers, or third-party charting libraries.

Object.is provides strict equality that accurately distinguishes NaN from NaN (equal) and +0 from -0 (different), ensuring reliable reference comparisons.

User interactions are events, not synchronizations. Code triggered by an event belongs directly in the event handler (e.g., onSubmit, onClick) rather than synchronizing through state and an effect.

Pass primitive properties instead (e.g., [user.id] instead of [user]), memoize the object with useMemo, or move the object declaration inside the effect.

React schedules useEffect to run after the DOM updates have been committed and the browser has painted the screen, keeping animations and user inputs smooth and responsive.