React Class Component Lifecycle vs Hooks Execution Mapping
React class components manage lifecycle through instance methods (`componentDidMount`, `componentDidUpdate`, `shouldComponentUpdate`), while functional components synchronize state with side-effects using the declarative hook pipeline (`useEffect`, `useLayoutEffect`, `useMemo`, `useRef`).
Why it matters in interviews
Visual & Interactive Explanation
Class Component Lifecycle vs React Hooks Mapping
Class Component Lifecycle
Imperative Lifecycle Methods
Modern React Hooks
Declarative Effect Synchronization
Code Examples & Implementation
// --- 1. CLASS COMPONENT (Fragmented Logic) ---
class UserProfileClass extends React.Component<{ userId: string }, { user: any; width: number }> {
state = { user: null, width: window.innerWidth };
componentDidMount() {
this.fetchUser(this.props.userId);
window.addEventListener("resize", this.handleResize);
}
componentDidUpdate(prevProps: { userId: string }) {
if (prevProps.userId !== this.props.userId) {
this.fetchUser(this.props.userId);
}
}
componentWillUnmount() {
window.removeEventListener("resize", this.handleResize);
}
handleResize = () => this.setState({ width: window.innerWidth });
fetchUser = (id: string) => fetchUser(id).then((u) => this.setState({ user: u }));
render() {
return <div>{this.state.user?.name} (Width: {this.state.width})</div>;
}
}
// --- 2. FUNCTIONAL COMPONENT WITH HOOKS (Colocated Concerns) ---
function UserProfileHooks({ userId }: { userId: string }) {
const [user, setUser] = useState(null);
const [width, setWidth] = useState(() => (typeof window !== "undefined" ? window.innerWidth : 0));
// Concern A: Data sync
useEffect(() => {
let active = true;
fetchUser(userId).then((u) => {
if (active) setUser(u);
});
return () => { active = false; };
}, [userId]);
// Concern B: Window resize
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return <div>{user?.name} (Width: {width})</div>;
}// CLASS: Always logs latest state because 'this.state' is mutated in place
class ClassCounter extends React.Component {
state = { count: 0 };
handleClick = () => {
this.setState({ count: this.state.count + 1 });
setTimeout(() => {
console.log("Class count after 2s:", this.state.count); // Logs LATEST count!
}, 2000);
};
}
// HOOKS: Captures 'count' at the moment of click (Closure Snapshot)
function HookCounter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
setTimeout(() => {
console.log("Hook count after 2s:", count); // Logs SNAPSHOT value (e.g. 0)!
}, 2000);
};
// Fix for latest value: use a ref (useRef) to emulate mutable instance
}Interview-Ready Answers
`componentDidMount` maps to `useEffect(..., [])`, `componentDidUpdate` maps to `useEffect(..., [deps])`, `componentWillUnmount` maps to effect cleanup functions, and `shouldComponentUpdate` maps to `React.memo`.
Follow-up Questions & Deep Dives
🎯 Code Output — What Does This Print?
function Counter() {
const [count, setCount] = useState(0);
const showCount = () => {
setTimeout(() => {
console.log("Count:", count);
}, 1000);
};
return (
<div>
<button onClick={() => { setCount(count + 1); showCount(); }}>
Increment & Log
</button>
</div>
);
}
// User clicks the button once. What is logged after 1 second?Common Mistakes & Anti-Patterns
- Thinking `useEffect` runs synchronously before paint like `componentDidMount` did
- Missing cleanup return functions on effects that subscribe to WebSockets or event listeners
- Attempting to write an Error Boundary using functional hooks
- Assuming `this.state` in classes and `useState` in functional components behave identically during async callbacks
Implementation Evolution: Anti-Pattern to Production
❌ Anti-Pattern: Treating `useEffect` as a Strict Imperative Lifecycle Method
// ❌ Anti-Pattern: Omitting dependencies to mimic "componentDidMount"
function ChatRoom({ roomId }: { roomId: string }) {
useEffect(() => {
const connection = createConnection(roomId);
connection.connect();
return () => connection.disconnect();
}, []); // ❌ ESLint warning! roomId change will NOT reconnect to new room!
return <div>Chat in room #{roomId}</div>;
}✅ Correct Pattern: Declarative Synchronization with Dependency Array
// ✅ Correct: Declare all used reactive values in dependencies
function ChatRoom({ roomId }: { roomId: string }) {
useEffect(() => {
const connection = createConnection(roomId);
connection.connect();
// Clean up old connection before connecting to next room
return () => connection.disconnect();
}, [roomId]);
return <div>Chat in room #{roomId}</div>;
}🚀 Production-Grade (Hardened): Production ErrorBoundary Component (Why Classes are Still Required)
// 🚀 Production: React ErrorBoundary (No hook equivalent exists in React 18/19)
import React, { Component, ErrorInfo, ReactNode } from "react";
interface Props {
fallback?: ReactNode | ((error: Error, reset: () => void) => ReactNode);
children: ReactNode;
}
interface State {
hasError: boolean;
error: Error | null;
}
export class ErrorBoundary extends Component<Props, State> {
public state: State = { hasError: false, error: null };
public static getDerivedStateFromError(error: Error): State {
// Update state so the next render will show the fallback UI
return { hasError: true, error };
}
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
// Log error to telemetry (e.g. Sentry / Datadog)
console.error("ErrorBoundary caught:", error, errorInfo);
}
public reset = () => {
this.setState({ hasError: false, error: null });
};
public render() {
if (this.state.hasError && this.state.error) {
if (typeof this.props.fallback === "function") {
return this.props.fallback(this.state.error, this.reset);
}
return this.props.fallback || (
<div className="p-4 border border-signal-red bg-signal-red/10 rounded-lg text-signal-red">
<h3>Something went wrong.</h3>
<button onClick={this.reset} className="mt-2 px-3 py-1 bg-signal-red text-white rounded">
Try Again
</button>
</div>
);
}
return this.props.children;
}
}⚠️ Trick Questions & Interviewer Traps
Q1:Why does React not provide a `useErrorBoundary` or `useCatch` hook?
Q2:How does `useLayoutEffect` differ from `componentDidMount`?
📋 Rapid Revision Cheat Sheet
- Mount: `componentDidMount` -> `useEffect(() => {}, [])`.
- Update: `componentDidUpdate` -> `useEffect(() => {}, [deps])`.
- Unmount: `componentWillUnmount` -> `return () => { /* cleanup */ }`.
- Bailout: `shouldComponentUpdate` -> `React.memo(Component, arePropsEqual)`.
- DOM Measurement: `getSnapshotBeforeUpdate` -> `useLayoutEffect`.
- Error Handling: `componentDidCatch` (Requires Class Component ErrorBoundary).
- Closure Trap: Hooks capture state snapshots; Classes read mutable `this.state`.
Real-World Architectural Scenario
A candidate refactors an old class component with a 3-second setTimeout alert to a functional component with hooks. When the user clicks the button and changes input text within 3 seconds, the alert shows the OLD text instead of the new text. Why?
Rate Your Readiness
Rate how comfortably you can explain this in an interview to update your global readiness gauge: