- React
- State Mechanics, Batching, and Lifecycle Transitions
- Lifecycle Transitions: Mount, Update & Unmount
Lifecycle Transitions: Mount, Update & Unmount
In functional React, components do not have lifecycle methods; instead, they synchronize state with external systems via useEffect. Mounting runs initial setup, updating re-runs effects whose dependencies changed after running cleanup, and unmounting runs final effect teardown.
Key Learning Objectives
Map legacy class lifecycle methods (componentDidMount, componentDidUpdate, componentWillUnmount) to useEffect.
Understand the setup and cleanup lifecycle in functional components.
Explain why thinking in "synchronization" is superior to thinking in "lifecycle events".
Diagnose why React StrictMode unmounts and remounts effects in development.
The Interview Problem & Context
Compare class component lifecycle methods to functional component effect synchronizations. How does the mental model of synchronization differ from discrete lifecycle hooks?
1// Legacy Class Component Lifecycle2class LegacyTimer extends React.Component {3 componentDidMount() {4 this.timerId = setInterval(this.tick, 1000);5 }6 componentDidUpdate(prevProps) {7 if (prevProps.roomId !== this.props.roomId) {8 this.reconnect();9 }10 }11 componentWillUnmount() {12 clearInterval(this.timerId);13 }14}1516// Modern Functional Component: Unified Synchronization Lifecycle17function ModernTimer({ roomId }: { roomId: string }) {18 useEffect(() => {19 // 1. Setup: Runs on mount AND on update if roomId changed20 const connection = createConnection(roomId);21 connection.connect();2223 // 2. Cleanup: Runs on unmount AND before re-running when roomId changes24 return () => {25 connection.disconnect();26 };27 }, [roomId]); // Synchronized with roomId28}
Deep Architectural Explanation
From Lifecycle Methods to Synchronization
In class components, lifecycle methods split related logic across three disconnected methods:
- Setup in
componentDidMount - Sync updates in
componentDidUpdate - Cleanup in
componentWillUnmount
Developers routinely forgot to update subscriptions incomponentDidUpdate, causing stale socket connections and memory leaks.
The Functional Synchronization Paradigm
In functional components, useEffect unifies setup and cleanup into a single declarative block:
- Mount: React runs the component render function, commits to the DOM, then runs the effect setup function.
- Update: When dependencies change, React runs the previous render's cleanup function, commits the new DOM, and runs the new effect setup function.
- Unmount: React runs the final cleanup function.
Development StrictMode Double-Mount
In React 18+, development mode intentionally mounts, unmounts, and re-mounts components:Setup -> Cleanup -> Setup
This immediately proves whether your cleanup function properly tears down subscriptions, timers, or abort controllers!
Common Interview Traps & Pitfalls
Treating useEffect as an exact synonym for componentDidMount by passing an empty array [] while reading dynamic props.
Omitting the cleanup function when adding event listeners or setting intervals, causing severe memory leaks.
Disabling React StrictMode because "my effects run twice in development" instead of fixing missing cleanups.
Senior Interviewer Follow-Up Probes
What is the exact execution order of cleanups and effects across parent and child components?
Why does React 18 execute effect cleanup before the next effect setup rather than waiting until unmount?
How does useLayoutEffect alter the timing of DOM mutation vs effect execution?
Frequently Asked Interview Questions
Why does React 18 run effects twice in development mode?
React StrictMode mounts, unmounts, and re-mounts components to ensure effects properly clean up their resources and support future features like Concurrent Fast Refresh and state preservation.
When does the useEffect cleanup function execute?
It executes right before the effect re-runs due to a dependency change, and when the component unmounts from the DOM.
What happens if you omit the dependency array in useEffect?
The effect will execute its setup and cleanup functions on every single render pass, which can cause severe performance issues or infinite loops if state is updated inside.
Why is thinking in "synchronization" better than thinking in "lifecycles"?
Because your code ensures the UI is always in sync with current props and state, avoiding common bugs where developers implemented mount logic but forgot to handle updates.
