Deep Dive8 min read · Updated 2025-06-01

React Component Lifecycle: From Class Methods to Hooks

Understand the three phases of a React component lifecycle — mounting, updating, and unmounting — and how each class lifecycle method maps to hooks.

💡 Practice these concepts interactively with AI feedback

Start Practicing →

React Component Lifecycle: From Class Methods to Hooks

Every React component has a lifecycle — it mounts, updates, and unmounts. Understanding this is foundational for any React interview.

The Three Phases

Mounting — component is created and inserted into the DOM for the first time.

Updating — component re-renders because props or state changed.

Unmounting — component is removed from the DOM.

Class Component Lifecycle Methods

class Timer extends React.Component {
  // 1. MOUNTING
  constructor(props) {
    super(props)
    this.state = { seconds: 0 }
    // initialize state, bind methods — no side effects here
  }

static getDerivedStateFromProps(props, state) { // runs before every render — return state update or null // rarely needed; usually a sign of over-engineering return null }

componentDidMount() { // fires ONCE after first DOM insertion // safe for: data fetching, subscriptions, DOM manipulation this.interval = setInterval(() => this.setState(s => ({ seconds: s.seconds + 1 })), 1000) }

// 2. UPDATING shouldComponentUpdate(nextProps, nextState) { // optimization: return false to skip re-render // React.PureComponent does shallow compare automatically return true }

componentDidUpdate(prevProps, prevState) { // fires after every update (not first render) if (prevProps.userId !== this.props.userId) { this.fetchUser(this.props.userId) // guard required — no infinite loop } }

// 3. UNMOUNTING componentWillUnmount() { // cleanup before removal — timers, subscriptions, listeners clearInterval(this.interval) }

render() { return <div>{this.state.seconds}s</div> } }

Hooks Equivalents

function Timer() {
  const [seconds, setSeconds] = useState(0)

// componentDidMount — empty deps array = runs once after mount useEffect(() => { const id = setInterval(() => setSeconds(s => s + 1), 1000)

// componentWillUnmount — cleanup function return () => clearInterval(id) }, [])

// componentDidUpdate — non-empty deps array useEffect(() => { document.title = 'Timer: ' + seconds }, [seconds])

return <div>{seconds}s</div> }

Lifecycle → Hook Mapping Table

| Class Method | Hook Equivalent | Notes | |---|---|---| | constructor | useState initializer | Lazy init: useState(() => fn()) | | componentDidMount | useEffect(fn, []) | Empty array = once after mount | | componentDidUpdate | useEffect(fn, [deps]) | Runs on mount too — add guard if needed | | componentWillUnmount | useEffect(() => cleanup, []) | Cleanup fn = unmount | | getDerivedStateFromProps | useState + useMemo | Compute derived state during render | | shouldComponentUpdate | React.memo | Wrap component for shallow prop compare | | getSnapshotBeforeUpdate | useRef | Store value before paint in a ref | | componentDidCatch | No hooks equivalent | Error boundaries are class-only |

Error Boundaries (Class Only)

Error boundaries catch errors in their child tree and display fallback UI. There is no hooks equivalent — they must be class components.

class ErrorBoundary extends React.Component {
  state = { hasError: false }

static getDerivedStateFromError(error) { // render phase — must be pure, update state only return { hasError: true } }

componentDidCatch(error, info) { // commit phase — safe for logging, analytics Sentry.captureException(error, { extra: info }) }

render() { if (this.state.hasError) return <h2>Something went wrong.</h2> return this.props.children } }

The UNSAFE_ Methods (Deprecated)

Three lifecycle methods were renamed with UNSAFE_ prefix in React 16.3:

  • UNSAFE_componentWillMount
  • UNSAFE_componentWillReceiveProps
  • UNSAFE_componentWillUpdate

They were deprecated because React's concurrent rendering can pause and restart renders — these methods could fire multiple times before a commit, making them unsafe for side effects or subscriptions.

Interview answer: "These methods caused bugs with async rendering. The UNSAFE_ prefix signals they'll be removed. componentWillMount → useEffect(fn, []). componentWillReceiveProps → getDerivedStateFromProps or useEffect with deps."

The useLayoutEffect Edge Case

useLayoutEffect fires synchronously after all DOM mutations but before the browser paints. Use it only for DOM measurements that must happen before the user sees the result:

// Measure a DOM node before paint to avoid flicker
useLayoutEffect(() => {
  const rect = ref.current.getBoundingClientRect()
  setPosition(rect)
}, [])

Default to useEffect. Switch to useLayoutEffect only if you see visual flicker.

Practice lifecycle questions at [JSPrep Pro](/auth).

📚 Practice These Topics
UseState
4–8 questions
UseCallback
4–8 questions
Custom hook
4–8 questions
UseMemo
4–8 questions

Put This Into Practice

Reading articles is passive. JSPrep Pro makes you actively recall, predict output, and get AI feedback.

Start Free →Browse All Questions

Related Articles

Deep Dive
We Built a RAG-Powered AI Question Engine Into a JavaScript Interview Platform — Here's Exactly How It Works
12 min read
Build Systems
Monorepo with Turborepo vs Nx: The Complete Comparison (2025)
9 min read
Core Concepts
map() vs forEach() in JavaScript: Which One to Use and Why It Matters
7 min read