Topic 4: Lifecycle Methods
📖 8 min read · 🎯 beginner · 🧭 Prerequisites: windows-installation, working-with-package-lock-json-to-lock-the-node-modules-versions
Why this matters
Here's the thing — when you write a React component, it doesn't just appear and stay forever. It gets created, updates when data changes, and eventually gets removed from the screen. And at each of those moments, React gives you a way to jump in and run your own code. That's what lifecycle methods are about. Want to fetch user data the moment a component loads? There's a hook for that. Need to clear a timer before a component disappears? There's a hook for that too. This lesson is about understanding those moments — and taking control of them.
What You'll Learn
- Identify the three lifecycle phases: Mounting, Updating, and Unmounting
- Use
componentDidMount,componentDidUpdate, andcomponentWillUnmountin class components - Replicate all three lifecycle phases in functional components using the
useEffecthook - Integrate multiple lifecycle-aware components into a single React app
The Analogy
Think of a React component like a theatre performer. When an actor walks on stage for the first time, the stage crew sets up their spotlight — that's mounting. While the performer delivers their lines and the scene evolves, the director calls adjustments — that's updating. When the scene ends and the actor exits stage-left, the crew strikes the set and turns off the spotlight to avoid wasting power — that's unmounting. Lifecycle methods are your backstage crew: they do the right work at exactly the right moment, keeping the show smooth and the electricity bill sane.
Chapter 1: Introduction to Lifecycle Methods
React lifecycle methods are special methods that execute at specific stages of a component's existence. They give you precise control over what happens when a component is created, re-rendered, or removed.
The three lifecycle phases:
- Mounting — The component is being created and inserted into the DOM.
- Updating — The component is being re-rendered due to changes in state or props.
- Unmounting — The component is being removed from the DOM.
stateDiagram-v2
[*] --> Mounting : component created
Mounting --> Updating : state or props change
Updating --> Updating : further state/props changes
Updating --> Unmounting : component removed
Mounting --> Unmounting : component removed immediately
Unmounting --> [*]
Each phase has dedicated methods in class components — and the useEffect hook covers all three in functional components.
Chapter 2: Mounting Phase Methods
The mounting phase fires once: the moment React inserts the component into the DOM.
componentDidMount
Called once, immediately after the component is added to the DOM. This is the right place to kick off API calls, set up subscriptions, or start timers — because the DOM is guaranteed to exist at this point.
src/components/MountingExample.js:
import React, { Component } from 'react';
class MountingExample extends Component {
constructor(props) {
super(props);
this.state = {
data: null
};
}
componentDidMount() {
// Simulate an API call
setTimeout(() => {
this.setState({ data: 'Data fetched' });
}, 2000);
}
render() {
return (
<div>
<h1>Mounting Phase Example</h1>
<p>{this.state.data || 'Loading...'}</p>
</div>
);
}
}
export default MountingExample;
When this component first mounts, data is null so the user sees "Loading...". After 2 seconds, setState triggers a re-render and "Data fetched" appears.
Chapter 3: Updating Phase Methods
The updating phase fires every time the component's state or props change and React re-renders it.
componentDidUpdate
Called after every re-render (except the initial mount). It receives prevProps and prevState so you can compare old and new values before acting — essential for avoiding infinite update loops.
src/components/UpdatingExample.js:
import React, { Component } from 'react';
class UpdatingExample extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
console.log('Count has been updated:', this.state.count);
}
}
render() {
return (
<div>
<h1>Updating Phase Example</h1>
<p>Count: {this.state.count}</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Increment
</button>
</div>
);
}
}
export default UpdatingExample;
The prevState.count !== this.state.count guard ensures the console.log fires only when count actually changes, not on every possible re-render.
Chapter 4: Unmounting Phase Methods
The unmounting phase fires once, right before React removes the component from the DOM.
componentWillUnmount
Called immediately before the component is destroyed. Use it to cancel timers, unsubscribe from data streams, or release any resources the component holds — otherwise they live on in memory even after the component is gone.
src/components/UnmountingExample.js:
import React, { Component } from 'react';
class UnmountingExample extends Component {
componentWillUnmount() {
console.log('Component is about to be unmounted');
}
render() {
return (
<div>
<h1>Unmounting Phase Example</h1>
</div>
);
}
}
export default UnmountingExample;
Integrating Lifecycle Components
With all three components built, the class wired them into App.js. A piece of state controls whether UnmountingExample is shown, which lets you trigger the unmount manually with a button click.
src/App.js:
import React, { useState } from 'react';
import './App.css';
import MountingExample from './components/MountingExample';
import UpdatingExample from './components/UpdatingExample';
import UnmountingExample from './components/UnmountingExample';
function App() {
const [showUnmountingExample, setShowUnmountingExample] = useState(true);
return (
<div className="App">
<h1>React Lifecycle Methods Example</h1>
<MountingExample />
<UpdatingExample />
{showUnmountingExample && <UnmountingExample />}
<button onClick={() => setShowUnmountingExample(false)}>
Remove Unmounting Example
</button>
</div>
);
}
export default App;
Clicking the button sets showUnmountingExample to false, which causes React to remove <UnmountingExample /> from the tree and trigger its componentWillUnmount.
Chapter 5: Using Hooks for Lifecycle Methods in Functional Components
Class components are the classic approach, but modern React favors functional components with hooks. The useEffect hook consolidates all three lifecycle phases into a single, composable API.
The useEffect Hook
useEffect accepts two arguments: a callback function and an optional dependencies array.
- No dependency array → runs after every render (like
componentDidUpdateon every change). - Empty array
[]→ runs once after the initial render (likecomponentDidMount). - Array with values
[count]→ runs after the initial render and whenever any listed value changes. - Returned function → React calls this before the next effect runs or when the component unmounts (like
componentWillUnmount).
src/components/FunctionalLifecycleExample.js:
import React, { useState, useEffect } from 'react';
function FunctionalLifecycleExample() {
const [data, setData] = useState(null);
const [count, setCount] = useState(0);
// This useEffect acts like componentDidMount and componentDidUpdate
useEffect(() => {
// Simulate an API call
const timer = setTimeout(() => {
setData('Data fetched');
}, 2000);
return () => {
// This return function acts like componentWillUnmount
clearTimeout(timer);
console.log('Component is about to be unmounted');
};
}, [count]); // Dependencies array
return (
<div>
<h1>Functional Lifecycle Example</h1>
<p>{data || 'Loading...'}</p>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default FunctionalLifecycleExample;
Because [count] is listed as a dependency, the effect re-runs every time count changes. The cleanup function (clearTimeout) runs before each re-execution, preventing multiple overlapping timers from stacking up.
Integrating FunctionalLifecycleExample
The class added the new functional component to App.js alongside the class-based examples, giving a side-by-side comparison of both approaches.
src/App.js:
import React, { useState } from 'react';
import './App.css';
import MountingExample from './components/MountingExample';
import UpdatingExample from './components/UpdatingExample';
import UnmountingExample from './components/UnmountingExample';
import FunctionalLifecycleExample from './components/FunctionalLifecycleExample';
function App() {
const [showUnmountingExample, setShowUnmountingExample] = useState(true);
return (
<div className="App">
<h1>React Lifecycle Methods Example</h1>
<MountingExample />
<UpdatingExample />
{showUnmountingExample && <UnmountingExample />}
<button onClick={() => setShowUnmountingExample(false)}>
Remove Unmounting Example
</button>
<FunctionalLifecycleExample />
</div>
);
}
export default App;
🧪 Try It Yourself
Task: Add a live clock to FunctionalLifecycleExample using useEffect.
- Declare a
timestate variable initialized tonew Date().toLocaleTimeString(). - Inside a
useEffectwith an empty dependency array[], start an interval withsetIntervalthat updatestimeevery second. - Return a cleanup function that calls
clearIntervalon the interval ID. - Render
<p>Current time: {time}</p>in the JSX.
Starter snippet:
import React, { useState, useEffect } from 'react';
function LiveClock() {
const [time, setTime] = useState(new Date().toLocaleTimeString());
useEffect(() => {
const interval = setInterval(() => {
setTime(new Date().toLocaleTimeString());
}, 1000);
return () => clearInterval(interval);
}, []);
return <p>Current time: {time}</p>;
}
export default LiveClock;
Success criterion: The time display updates every second in the browser. When you remove the component (wrap it in a conditional like showUnmountingExample), the interval stops and you see no console errors about state updates on an unmounted component.
🔍 Checkpoint Quiz
Q1. Which lifecycle phase runs only once — immediately after a component is first added to the DOM?
A) Updating
B) Unmounting
C) Mounting
D) Rendering
Q2. Given this snippet, what does the console print the second time the button is clicked?
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
console.log('Count has been updated:', this.state.count);
}
}
// initial state: { count: 0 }
// button onClick: this.setState({ count: this.state.count + 1 })
A) Count has been updated: 0
B) Count has been updated: 1
C) Count has been updated: 2
D) Nothing — the guard prevents any logging
Q3. What is the purpose of the function returned from inside a useEffect callback?
A) It provides the component's initial state
B) It runs cleanup before the next effect fires or when the component unmounts
C) It replaces the render method in functional components
D) It triggers a forced re-render of the component
Q4. You have a component that subscribes to a WebSocket on mount. Where should you close the WebSocket connection, and why?
A1. C) Mounting — componentDidMount fires once after the initial DOM insertion; the other phases require an already-mounted component.
A2. C) Count has been updated: 2 — the first click sets count to 1, the second click sets it to 2. componentDidUpdate fires after each click, and since prevState.count differs from this.state.count each time, the log fires with the new value.
A3. B) It runs cleanup before the next effect fires or when the component unmounts — this mirrors componentWillUnmount for teardown logic like clearing timers or cancelling subscriptions.
A4. Close the WebSocket inside componentWillUnmount (class component) or the cleanup function returned from useEffect (functional component). If you skip this, the open socket keeps firing callbacks even after the component is gone, causing memory leaks and potential state-update errors on unmounted components.
🪞 Recap
- React components pass through three lifecycle phases: Mounting, Updating, and Unmounting.
componentDidMountruns once after the first render — use it for API calls and subscriptions.componentDidUpdate(prevProps, prevState)runs after every re-render — guard comparisons prevent infinite loops.componentWillUnmountruns before the component is destroyed — use it to cancel timers and clean up resources.- The
useEffecthook covers all three phases in functional components: the callback handles mount/update, the returned function handles cleanup/unmount, and the dependency array controls when the effect re-runs.
📚 Further Reading
- React Docs — Component Lifecycle — official reference for all class lifecycle methods
- React Docs — useEffect — full specification for the hook including edge cases and escape hatches
- A Complete Guide to useEffect — Dan Abramov's deep-dive on the mental model behind
useEffectand dependency arrays - ⬅️ Previous: Working with package-lock.json to Lock the Node Modules Versions
- ➡️ Next: PHP with Update & Delete