Intermediate1 questionFull Guide

React Component Lifecycle — Complete Interview Guide

Master every phase of the React component lifecycle — mounting, updating, and unmounting. Covers class component methods, their exact hooks equivalents, error boundaries, and the deprecated methods every interviewer still asks about.

The Mental Model

Think of a React component's lifecycle like a theatre performance. Mounting is when the actor walks on stage — the component is born, setup happens, the audience sees it for the first time. Updating is the performance itself — the actor reacts to new cues (props or state changes) and adapts. Unmounting is the curtain call — the actor exits and everything brought on stage must be cleared away. Class components gave you named hooks for each moment in that performance; the functional component model unified everything into effects, but the same three-act structure runs underneath.

The Explanation

The Three Lifecycle Phases

Every React component, whether a class or a function, goes through three phases:

  1. Mounting — the component is created and inserted into the DOM for the first time
  2. Updating — the component re-renders because props or state changed
  3. Unmounting — the component is removed from the DOM

Class components expose explicit lifecycle methods for each phase. Functional components achieve the same result through hooks — primarily useEffect, useState, and useRef. Both models produce identical behaviour; the syntax is just different.

Mounting Phase — Class Component

Four methods fire in this order when a class component first appears in the DOM:

class UserProfile extends React.Component {
  constructor(props) {
    super(props);
    // 1. FIRST — initialise state and bind methods
    // Do NOT call setState() or trigger side effects here
    this.state = { user: null, loading: true };
  }

  static getDerivedStateFromProps(props, state) {
    // 2. Called before EVERY render (mount and update)
    // Return an object to merge into state, or null to change nothing
    // Almost always the wrong tool — see Common Mistakes
    return null;
  }

  render() {
    // 3. The only required method — must return JSX
    // Must be PURE — no side effects, no setState calls
    const { user, loading } = this.state;
    if (loading) return <Spinner />;
    return <div>{user.name}</div>;
  }

  componentDidMount() {
    // 4. LAST — fires after the component is in the DOM
    // Safe to: fetch data, add event listeners, access DOM nodes via refs
    fetch(`/api/users/${this.props.userId}`)
      .then(res => res.json())
      .then(user => this.setState({ user, loading: false }));
  }
}

Updating Phase — Class Component

When props or state change, this sequence fires:

  shouldComponentUpdate(nextProps, nextState) {
    // Called before re-render — return false to bail out and skip the render
    // React.PureComponent does a shallow prop/state comparison automatically
    // Only use this for measurable performance bottlenecks
    return nextProps.userId !== this.props.userId;
  }

  // render() fires again here

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // Called right BEFORE the DOM is mutated (after render, before commit)
    // Whatever you return here is passed as the 3rd argument to componentDidUpdate
    // Classic use case: capture scroll position before a list grows
    if (prevProps.messages.length < this.props.messages.length) {
      return this.listRef.current.scrollHeight;
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // Called after every re-render and DOM update
    // ALWAYS guard setState calls with a condition — otherwise infinite loop
    if (prevProps.userId !== this.props.userId) {
      this.fetchUser(this.props.userId);
    }
    // snapshot is the value returned from getSnapshotBeforeUpdate
    if (snapshot !== null) {
      this.listRef.current.scrollTop =
        this.listRef.current.scrollHeight - snapshot;
    }
  }

Unmounting Phase — Class Component

  componentWillUnmount() {
    // Called just before the component is removed from the DOM
    // CLEAN UP everything: subscriptions, timers, event listeners, pending fetches
    // Do NOT call setState here — the component is being destroyed
    clearInterval(this.timerID);
    this.socket.close();
    window.removeEventListener('resize', this.handleResize);
  }

The Complete Class-to-Hooks Mapping

Functional components don't have lifecycle methods — they have effects. Every class lifecycle method maps directly to a hooks pattern:

constructor → useState / useRef initialisation

// Class
constructor(props) {
  super(props);
  this.state = { count: 0 };
}

// Hooks
const [count, setCount] = useState(0);

componentDidMount → useEffect with []

// Class
componentDidMount() {
  this.fetchUser(this.props.userId);
}

// Hooks — empty array = run once on mount
useEffect(() => {
  fetchUser(userId);
}, []);

componentDidUpdate → useEffect with deps

// Class
componentDidUpdate(prevProps) {
  if (prevProps.userId !== this.props.userId) {
    this.fetchUser(this.props.userId);
  }
}

// Hooks — the dependency array handles the comparison automatically
useEffect(() => {
  fetchUser(userId);
}, [userId]); // Re-runs whenever userId changes

componentWillUnmount → useEffect cleanup function

// Class
componentWillUnmount() {
  this.subscription.unsubscribe();
}

// Hooks — the returned function is the cleanup
useEffect(() => {
  const sub = subscribe(userId);
  return () => sub.unsubscribe(); // this IS componentWillUnmount
}, []);

shouldComponentUpdate → React.memo

// Class
shouldComponentUpdate(nextProps) {
  return nextProps.id !== this.props.id; // return false to skip re-render
}

// Hooks — React.memo wraps the component, comparator returns true to SKIP render
// (Note: the boolean is INVERTED compared to shouldComponentUpdate)
const UserCard = React.memo(({ id, name }) => {
  return <div>{name}</div>;
}, (prevProps, nextProps) => {
  return prevProps.id === nextProps.id; // true = props are equal, skip render
});
The comparator in React.memo returns true to skip re-render (props are equal). shouldComponentUpdate returns true to allow re-render. The logic is inverted — this trips up many candidates.

getSnapshotBeforeUpdate → useLayoutEffect + ref

// Hooks approximation — useLayoutEffect fires synchronously before paint
const scrollRef = useRef(null);
const prevLengthRef = useRef(messages.length);

useLayoutEffect(() => {
  if (messages.length > prevLengthRef.current) {
    // Restore scroll position after new messages are added
    scrollRef.current.scrollTop = scrollRef.current.scrollHeight - snapshot;
  }
  prevLengthRef.current = messages.length;
});

Error Lifecycle — The Only Reason to Still Write Class Components

Two lifecycle methods exist for catching errors thrown during rendering in child components. There is no hooks equivalent — you must write a class component as an Error Boundary:

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    // Called during the render phase when a child throws
    // Return state update to show the fallback UI
    return { hasError: true, error };
  }

  componentDidCatch(error, info) {
    // Called in the commit phase after the fallback UI renders
    // Safe to log errors to an external service
    logToSentry(error, info.componentStack);
  }

  render() {
    if (this.state.hasError) {
      return <div>Something went wrong: {this.state.error.message}</div>;
    }
    return this.props.children;
  }
}

// Usage — wraps any subtree
<ErrorBoundary>
  <MyComplexFeature />
</ErrorBoundary>
Error Boundaries only catch errors in child components during rendering, lifecycle methods, and constructors. They do NOT catch errors in event handlers (use try/catch there) or async code.

The Deprecated Lifecycle Methods

Three methods were deprecated in React 16.3 and prefixed with UNSAFE_. They still work today but are removed in future React versions. Know them for interviews — many legacy codebases still use them:

  • UNSAFE_componentWillMount — ran before mount; replace with constructor or useEffect(fn, [])
  • UNSAFE_componentWillReceiveProps — ran when new props arrived; replace with getDerivedStateFromProps or useEffect(fn, [prop])
  • UNSAFE_componentWillUpdate — ran before re-render; replace with getSnapshotBeforeUpdate

Why deprecated? React's concurrent rendering can start a render, pause it, and restart — running these methods multiple times before committing anything to the DOM. Any side effects inside them (data fetching, subscriptions) would fire multiple times unexpectedly, causing subtle bugs.

Functional Component Lifecycle — The Real Mental Model

Functional components don't technically have a "lifecycle" — every render is just a function call that returns JSX. The lifecycle emerges from effects and how React schedules them:

  1. Mount: function runs → DOM updated → browser paints → useEffect(fn, []) fires
  2. Update: function runs again → DOM updated → browser paints → useEffect(fn, [dep]) fires if dep changed → cleanup of previous effect runs first
  3. Unmount: cleanup functions of all active effects run, in the order they were declared

If you have multiple useEffect calls in one component, they run top to bottom in the order they appear in the code.

Common Misconceptions

⚠️

Many developers think functional components don't have a lifecycle — they do. Mounting, updating, and unmounting all happen exactly the same way; the difference is that hooks express lifecycle as synchronisation effects rather than named callback methods.

⚠️

Many developers treat componentDidMount and useEffect(fn, []) as identical — they're almost the same, with one difference: in React 18 StrictMode, useEffect fires twice on mount (mount → unmount → mount) to test cleanup. componentDidMount fires once even in StrictMode.

⚠️

Many developers reach for getDerivedStateFromProps when props change and they want to update state — this is almost always wrong. If you can compute a value from props, compute it inline during render: const derivedValue = computeFrom(props). getDerivedStateFromProps was added to handle rare edge cases like a controlled animation component that resets on prop change.

⚠️

Many developers call setState in componentDidUpdate without a condition — this creates an infinite loop: update → componentDidUpdate → setState → update → componentDidUpdate. Always wrap setState in a condition that compares prevProps or prevState.

⚠️

Many developers think React.memo's comparator works the same as shouldComponentUpdate — the boolean is inverted. shouldComponentUpdate returns true to ALLOW a render. React.memo's comparator returns true to SKIP a render (meaning props are equal). Mixing this up is a very common interview mistake.

⚠️

Many developers think Error Boundaries catch all errors — they only catch errors thrown during rendering, in lifecycle methods, and in constructors of child components. They do not catch errors in event handlers, async code (setTimeout, fetch), or server-side rendering.

Where You'll See This in Real Code

Data fetching on mount: every user profile page, dashboard, and product detail page uses componentDidMount (class) or useEffect(fn, [id]) (hooks) to fetch data when the component first appears or when a route param changes.

Subscription teardown: real-time features (WebSocket feeds, Firestore listeners, Redux store subscriptions) set up in componentDidMount and torn down in componentWillUnmount — or in useEffect's cleanup function — to prevent memory leaks and duplicate listeners.

Scroll position restoration: chat applications use getSnapshotBeforeUpdate to capture the scroll height before new messages are appended, then adjust scrollTop in componentDidUpdate so the view doesn't jump — the only lifecycle method that has a clean hooks equivalent requiring useLayoutEffect.

Error Boundaries in production apps: every serious React application wraps feature sections in an ErrorBoundary so a crash in one widget (a chart, a media player) doesn't take down the whole page. Libraries like react-error-boundary provide a ready-made wrapper.

Performance optimisation with shouldComponentUpdate: heavy list items (complex cards, table rows) in class components use shouldComponentUpdate or extend React.PureComponent to skip re-renders when props haven't changed — the hooks equivalent is wrapping with React.memo.

Third-party library teardown: map libraries (Mapbox, Leaflet), rich text editors (Quill, TipTap), and charting libraries (D3, Chart.js) initialise in componentDidMount and call their destroy/remove methods in componentWillUnmount to free GPU memory and detach DOM nodes.

Interview Cheat Sheet

  • Mounting order: constructor → getDerivedStateFromProps → render → componentDidMount
  • Updating order: getDerivedStateFromProps → shouldComponentUpdate → render → getSnapshotBeforeUpdate → componentDidUpdate
  • Unmounting: componentWillUnmount (clean up everything — timers, listeners, subscriptions)
  • componentDidMount → useEffect(fn, []) | componentDidUpdate → useEffect(fn, [deps]) | componentWillUnmount → return fn from useEffect
  • shouldComponentUpdate → React.memo — but the comparator boolean is INVERTED (true = skip render in memo, true = allow render in shouldComponentUpdate)
  • getDerivedStateFromProps: almost always wrong — compute derived values directly in the render body instead
  • getSnapshotBeforeUpdate: no clean hooks equivalent — approximate with useLayoutEffect + ref
  • Error Boundaries require a class component — no hooks equivalent exists (use react-error-boundary library)
  • Deprecated (UNSAFE_): componentWillMount, componentWillReceiveProps, componentWillUpdate — deprecated because concurrent mode can run them multiple times before committing
  • Multiple useEffect calls run top-to-bottom; cleanup of the previous effect runs before the next effect fires
💡

How to Answer in an Interview

  • 1.Lead with the three phases — mounting, updating, unmounting — before naming any methods. Interviewers want to see a mental model, not just method memorisation. Say: 'React components go through three phases: mounting when first added to the DOM, updating when props or state change, and unmounting when removed.'
  • 2.Know the class → hooks mapping cold: componentDidMount = useEffect(fn, []), componentDidUpdate = useEffect(fn, [deps]), componentWillUnmount = return fn from useEffect. Being able to rewrite a class lifecycle in hooks (or vice versa) is a standard mid-level question.
  • 3.The getDerivedStateFromProps answer: 'It's called before every render and lets you derive state from props — but in practice it's almost always the wrong tool. If you can compute a value from props, compute it directly in the render body and avoid the extra complexity.'
  • 4.Error Boundaries are the one place you must still write a class component in modern React — there is no hooks equivalent. Lead with this when asked about class components: 'I use functional components for everything except Error Boundaries, which require getDerivedStateFromError and componentDidCatch.'
  • 5.The React.memo comparator gotcha is a classic interview trap: it returns true to SKIP a render, opposite of shouldComponentUpdate which returns true to ALLOW a render. State this clearly if asked to compare them.
  • 6.If asked about the deprecated lifecycle methods, explain WHY they were deprecated: 'React's concurrent mode can pause and restart renders, so those methods could fire multiple times before a commit. Any side effects inside them would run multiple times, causing bugs. They were renamed UNSAFE_ to signal this.'
📖 Deep Dive Articles
React Common Mistakes: 8 Bugs That Catch Developers Off Guard9 min readTop 50 React Interview Questions (2025 Edition)15 min read

Practice Questions

1 question
#01

What is the component lifecycle and how do hooks map to it?

EasyReact Fundamentals PRO💡 Mount → Update → Unmount — useEffect covers all three phases

Related Topics

useState Hook — Complete React Interview Guide
Beginner·4–8 Qs
Rendering Reconciliation
Advanced·4–8 Qs
React Fiber Architecture Interview Questions
Advanced·4–8 Qs
useEffect Hook — Complete React Interview Guide
Beginner·4–8 Qs
🎯

Can you answer these under pressure?

Reading answers is not the same as knowing them. Practice saying them out loud with AI feedback — that's what builds real interview confidence.

Practice Free →Try Output Quiz