- React
- Core React Hooks: State, Synchronization, and Refs
- useEffect Mental Model: Synchronization Over Lifecycle
useEffect Mental Model: Synchronization Over Lifecycle
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';23interface ChatRoomProps {4 roomId: string;5 serverUrl: string;6}78export function ChatRoom({ roomId, serverUrl }: ChatRoomProps) {9 useEffect(() => {10 // 1. Synchronize: Connect to the external chat room system11 const connection = createConnection({ serverUrl, roomId });12 connection.connect();1314 // 2. Disconnect: Clean up when roomId, serverUrl changes, or unmounts15 return () => {16 connection.disconnect();17 };18 }, [roomId, serverUrl]); // All reactive values read inside the effect must be listed!1920 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
Why does useEffect compare dependencies using Object.is?
Object.is provides strict equality that accurately distinguishes NaN from NaN (equal) and +0 from -0 (different), ensuring reliable reference comparisons.
Why shouldn't you use useEffect for handling user button clicks?
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.
How do you prevent an effect from re-running when a dependency is an object?
Pass primitive properties instead (e.g., [user.id] instead of [user]), memoize the object with useMemo, or move the object declaration inside the effect.
Why does useEffect not block browser rendering?
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.
