---
title: "05 — The Component Lifecycle — Mount, Update, Unmount, and Why Hooks Replaced It"
uid: component-lifecycle
tags: ["useeffect", "lifecycle", "react", "roadmap:react", "class-components", "hooks"]
excerpt: "Every component passes through mount, update, unmount — and modern functional code expresses all three through useEffect, not through a dozen class methods."
date: 2026-08-13T03:27:45+0000
source: https://www.aveshina.my.id/en/blog/component-lifecycle
---

A wall of componentDidMount-style methods was my first meeting with the component lifecycle, and I had to relearn it as something simpler. The model that made it click: **every component moves through three phases — mount, update, unmount — and modern functional code expresses all three through the useEffect hook, not through a dozen class methods.** [1][2] The class lifecycle is legacy knowledge; the lifecycle itself is not.

## The three phases

Regardless of class vs function, a component's life has three phases [1][2]:

- **Mounting** — the component is added to the DOM for the first time. Initial render runs, then any setup effects.
- **Updating** — state or props change, the component re-renders, and effects may re-run depending on their dependencies.
- **Unmounting** — the component is removed from the DOM. Any cleanup from prior effects runs here.

That's the entire lifecycle. The question is only how I tap into each phase.

## The class-era methods (and why they're mostly gone)

Class components exposed a method for each moment in the cycle — componentDidMount for initial setup, componentDidUpdate for responding to changes, componentWillUnmount for teardown, plus the now-deprecated componentWillMount/componentWillReceiveProps/componentWillUpdate trio [2]. I list them because I still read them in older code, but the official guidance is clear: don't write new class components, and don't reach for these methods. They split related logic across multiple methods (the setup lives in DidMount, the cleanup in WillUnmount, the dependency check in DidUpdate), which is exactly the fragmentation hooks were designed to fix.

```figure
<svg viewBox="0 0 720 300" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="Lifecycle timeline. A horizontal line labeled mount, then a repeated update loop, then unmount. Above the line, faded boxes for the class methods componentDidmount, componentDidUpdate, componentWillUnmount. Below the line, useEffect blocks aligned to the same phases — one effect handles setup, re-run, and cleanup together.">
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">
    <!-- timeline -->
    <line x1="60" y1="150" x2="660" y2="150" stroke="#374151" stroke-width="2"/>
    <circle cx="80" cy="150" r="7" fill="#16a34a"/>
    <circle cx="360" cy="150" r="7" fill="#ca8a04"/>
    <circle cx="640" cy="150" r="7" fill="#dc2626"/>

    <text x="80" y="180" font-size="12" font-weight="700" fill="#16a34a" text-anchor="middle">MOUNT</text>
    <text x="360" y="180" font-size="12" font-weight="700" fill="#ca8a04" text-anchor="middle">UPDATE (repeats)</text>
    <text x="640" y="180" font-size="12" font-weight="700" fill="#dc2626" text-anchor="middle">UNMOUNT</text>

    <!-- class methods (faded, above) -->
    <rect x="20" y="60" width="200" height="30" rx="6" fill="#fee2e2" stroke="#fca5a5" stroke-width="1" opacity="0.7"/>
    <text x="120" y="79" font-size="10" font-family="ui-monospace,monospace" fill="#7f1d1d" text-anchor="middle">componentDidMount</text>

    <rect x="260" y="60" width="200" height="30" rx="6" fill="#fee2e2" stroke="#fca5a5" stroke-width="1" opacity="0.7"/>
    <text x="360" y="79" font-size="10" font-family="ui-monospace,monospace" fill="#7f1d1d" text-anchor="middle">componentDidUpdate</text>

    <rect x="500" y="60" width="220" height="30" rx="6" fill="#fee2e2" stroke="#fca5a5" stroke-width="1" opacity="0.7"/>
    <text x="610" y="79" font-size="10" font-family="ui-monospace,monospace" fill="#7f1d1d" text-anchor="middle">componentWillUnmount</text>

    <text x="360" y="44" font-size="10" fill="#9ca3af" text-anchor="middle" font-style="italic">class era — one phase split across many methods</text>

    <!-- hook (below) -->
    <rect x="200" y="210" width="320" height="40" rx="8" fill="#e0e7ff" stroke="#6366f1" stroke-width="1.5"/>
    <text x="360" y="228" font-size="11" font-weight="700" fill="#1e1b4b" text-anchor="middle">useEffect(() => { setup; return cleanup }, [deps])</text>
    <text x="360" y="244" font-size="9.5" fill="#4338ca" text-anchor="middle">setup on mount · re-run on dep change · cleanup on unmount</text>

    <text x="360" y="280" font-size="10" fill="#9ca3af" text-anchor="middle" font-style="italic">functional era — one hook expresses all three phases</text>
  </g>
</svg>
```

## useEffect: one hook for all three phases

useEffect is the functional-era replacement for the lifecycle methods, and its genius is that it unifies setup, update, and cleanup into a single construct [1][3]. An effect is a function that:

- runs after mount (replacing componentDidMount),
- runs again whenever its dependency array changes (replacing componentDidUpdate),
- optionally returns a cleanup function that runs before the next run and on unmount (replacing componentWillUnmount).

```
useEffect(() => {
  const id = setInterval(tick, 1000);   // setup — mount + dep changes
  return () => clearInterval(id);         // cleanup — before next run + unmount
}, []);                                   // deps: run once on mount
```

That one block covers all three phases. The dependency array is the lever: [] means "run on mount only," [a, b] means "run on mount and whenever a or b changes," and omitting the array entirely means "run after every render" (almost never what I want).

## The effect lifecycle, in detail

The official docs frame effects in terms of a **reactive effect lifecycle** — synchronize, then resynchronize, then clean up [1]. Each effect is its own lifecycle, independent of the component's other effects:

1. On mount, React runs the effect (setup).
2. If a dependency changes, React first runs the previous effect's cleanup, then runs the new effect (resynchronize).
3. On unmount, React runs the final cleanup.

The practical consequence: **every side effect I create must have a matching teardown.** A subscription needs an unsubscribe. A timer needs a clearInterval. An event listener needs a removeEventListener. Forgetting cleanup is the single most common useEffect bug — it leaks listeners, fires timers on unmounted components, and double-fires in Strict Mode (which intentionally runs effects twice to surface exactly this class of bug).

## When effects are NOT the answer

The lifecycle framing can make useEffect feel like the answer to everything. It isn't. Effects are for **synchronizing with external systems** — fetching, subscribing, talking to browser APIs, integrating with non-React libraries [1]. They are not for:

- **Transforming data** — if a value can be computed from existing state/props during render, compute it during render, not in an effect.
- **Responding to user events** — event handlers run on the event; effects run after render. Putting event logic in an effect creates stale-state bugs.
- **Resetting state on prop change** — the official guidance is to render a fresh component (via key) rather than sync state in an effect.

The rule of thumb I use: if I'm writing an effect and the body doesn't touch anything outside React, I've probably made a mistake. Effects exist to reach across the boundary to the outside world.

## How I use this

I never write class components, so the lifecycle methods are read-only knowledge for me. For new code I reach for useEffect with three habits baked in: always pair setup with cleanup, always pass an explicit dependency array (never omit it), and ask "is this really synchronizing with an external system?" before I write the effect. When I find myself using an effect to derive state, I refactor to compute during render; when I find myself using one to react to a user action, I move the logic into the event handler. The lifecycle is still real, but I express it through one hook and only when I genuinely need to cross out of React.

## References

[1] React team, "Lifecycle of reactive effects," react.dev, 2024. [Online]. Available: [https://react.dev/learn/lifecycle-of-reactive-effects](https://react.dev/learn/lifecycle-of-reactive-effects)

[2] React team, "Class Component (legacy reference)," react.dev, 2024. [Online]. Available: [https://react.dev/reference/react/Component](https://react.dev/reference/react/Component)

[3] React team, "You might not need an Effect," react.dev, 2024. [Online]. Available: [https://react.dev/learn/you-might-not-need-an-effect](https://react.dev/learn/you-might-not-need-an-effect)

[4] W. Wojtekmaj, "React lifecycle methods diagram," projects.wojtekmaj.pl, 2023. [Online]. Available: [https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/](https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/)

[5] R. Wieruch, "React component lifecycle: methods and hooks," tsh.io blog, 2023. [Online]. Available: [https://tsh.io/blog/react-component-lifecycle-methods-vs-hooks/](https://tsh.io/blog/react-component-lifecycle-methods-vs-hooks/)

```quiz
Q: A component's lifecycle has three phases:
- mount, update, unmount
- render, commit, paint
- create, fetch, destroy
correct: 0
explain: Every component mounts (added to DOM), updates (re-renders on state/prop change), and unmounts (removed from DOM). Class components exposed methods for each; functional components express all three through useEffect.

Q: What does an empty dependency array [] in useEffect do?
- runs the effect after every render
- runs the effect once on mount, with cleanup on unmount
- never runs the effect
correct: 1
explain: An empty deps array means the effect has no reactive dependencies, so it runs once after mount and its cleanup runs once on unmount.

Q: Why does React Strict Mode run effects twice in development?
- to make the app faster
- to surface missing cleanup by simulating mount → unmount → mount
- it is a bug in React
correct: 1
explain: Strict Mode intentionally double-invokes setup and cleanup to expose effects that don't tear down after themselves — a leak in dev is a leak in prod.

Q: You're computing a filtered list from existing state. Where should that happen?
- in a useEffect, then setState
- during render — derived state computes directly from props/state
- in a class lifecycle method
correct: 1
explain: If a value can be derived from existing state/props, compute it during render. Effects are for synchronizing with external systems, not for transforming data that lives inside React.

Q: The most common useEffect bug is…
- forgetting the cleanup function for a side effect you started
- using too many keys
- returning JSX from the effect
correct: 0
explain: Every side effect (subscription, timer, listener) needs matching teardown. Missing cleanup leaks resources and double-fires in Strict Mode.
```
