Topic 4: Lifecycle Methods
📖 6 min read · 🎯 beginner · 🧭 Prerequisites: react-structure, react-components
⚠️ Note: this lesson was regenerated because the source content did not match the title.
Why this matters
Here's the thing — when you first start building React components, it feels like they just appear on the screen. But a lot is actually happening behind the scenes. Every component goes through a predictable journey: it mounts, it updates when data changes, and eventually it unmounts and disappears. Understanding this lifecycle is what lets you control when your code runs — when to fetch data from an API, when to set up a timer, and crucially, when to clean up after yourself so you don't leave memory leaks behind.
What You'll Learn
- Identify the three phases of a React component's lifecycle: mounting, updating, and unmounting
- Use the key class-component lifecycle methods:
componentDidMount,componentDidUpdate,componentWillUnmount,shouldComponentUpdate,getDerivedStateFromProps, andgetSnapshotBeforeUpdate - Implement
useEffectto replicate lifecycle behavior in functional components - Handle errors with
componentDidCatchandgetDerivedStateFromError
The Analogy
Think of a React component like a Vizag apartment. When a tenant moves in, the landlord runs an inspection and sets everything up — that's mounting. While the tenant lives there, things change: furniture is rearranged, appliances are swapped — that's updating. When the tenant moves out, the landlord cleans up, cancels subscriptions, and hands the keys back — that's unmounting. React's lifecycle methods are the landlord's checklist for each of those moments, guaranteeing nothing is left running after the tenant is gone and nothing is skipped when they arrive.
Chapter 1: The Three Phases
Every React component travels through a predictable sequence of phases from birth to removal.
flowchart LR
A([Mount]) --> B([Update])
B --> B
B --> C([Unmount])
| Phase | Trigger | Purpose |
|---|---|---|
| Mounting | Component appears in the DOM for the first time | Initial render, data fetching, subscriptions |
| Updating | Props or state change | Re-render, syncing side effects |
| Unmounting | Component is removed from the DOM | Cleanup: timers, subscriptions, listeners |
React exposes hooks into each phase through lifecycle methods (class components) and the useEffect hook (functional components).
Chapter 2: Class Component Lifecycle Methods
Mounting Phase
When a class component mounts, React calls these methods in order:
constructor(props)— initialize state, bind methodsstatic getDerivedStateFromProps(props, state)— sync state from props before first renderrender()— return JSXcomponentDidMount()— runs after the component is inserted into the DOM
componentDidMount is the go-to place for API calls, DOM measurements, and starting subscriptions.
class UserProfile extends React.Component {
constructor(props) {
super(props);
this.state = { user: null, loading: true };
}
componentDidMount() {
fetch(`/api/users/${this.props.userId}`)
.then((res) => res.json())
.then((user) => this.setState({ user, loading: false }));
}
render() {
const { user, loading } = this.state;
if (loading) return <p>Loading...</p>;
return <h1>{user.name}</h1>;
}
}
Updating Phase
When props or state change, React calls:
static getDerivedStateFromProps(props, state)— runs on every rendershouldComponentUpdate(nextProps, nextState)— returnfalseto bail out of re-render (performance optimization)render()getSnapshotBeforeUpdate(prevProps, prevState)— captures DOM info (e.g., scroll position) before the DOM is updatedcomponentDidUpdate(prevProps, prevState, snapshot)— runs after the DOM has updated
class ScrollableList extends React.Component {
listRef = React.createRef();
getSnapshotBeforeUpdate(prevProps, prevState) {
if (prevProps.items.length < this.props.items.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
<ul ref={this.listRef}>
{this.props.items.map((item) => (
<li key={item.id}>{item.text}</li>
))}
</ul>
);
}
}
shouldComponentUpdate lets you skip a re-render when you know nothing meaningful changed:
shouldComponentUpdate(nextProps, nextState) {
return nextProps.userId !== this.props.userId;
}
Unmounting Phase
React calls one method when a component is removed:
componentWillUnmount()— cancel timers, unsubscribe from services, remove event listeners
class LiveTicker extends React.Component {
componentDidMount() {
this.intervalId = setInterval(() => {
this.setState({ time: new Date().toLocaleTimeString() });
}, 1000);
}
componentWillUnmount() {
clearInterval(this.intervalId); // prevents memory leak
}
render() {
return <p>Current time: {this.state?.time}</p>;
}
}
getDerivedStateFromProps in Detail
This is a static method that returns an object to merge into state, or null to update nothing. It runs before every single render — both mounting and updating.
class TemperatureDisplay extends React.Component {
state = { celsius: 0 };
static getDerivedStateFromProps(props, state) {
if (props.fahrenheit !== undefined) {
return { celsius: ((props.fahrenheit - 32) * 5) / 9 };
}
return null;
}
render() {
return <p>{this.state.celsius.toFixed(1)}°C</p>;
}
}
Chapter 3: Error Boundaries
React provides two lifecycle methods for catching errors in the component tree below a class component.
static getDerivedStateFromError(error)— update state to show a fallback UIcomponentDidCatch(error, info)— log the error to a reporting service
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, info) {
console.error('Caught error:', error, info.componentStack);
}
render() {
if (this.state.hasError) {
return <h2>Something went wrong. Please refresh.</h2>;
}
return this.props.children;
}
}
Wrap any subtree with <ErrorBoundary> to prevent a single component crash from breaking the whole app:
<ErrorBoundary>
<UserProfile userId={42} />
</ErrorBoundary>
Chapter 4: useEffect — Lifecycle in Functional Components
Functional components use the useEffect hook to replicate all three lifecycle phases. Its signature is:
useEffect(() => {
// side effect code (componentDidMount + componentDidUpdate)
return () => {
// cleanup (componentWillUnmount)
};
}, [dependencies]);
The dependency array controls when the effect runs:
| Dependency array | When effect runs |
|---|---|
| Omitted | After every render |
[] (empty) | Once after mount only |
[a, b] | After mount, and whenever a or b changes |
Equivalent of componentDidMount
import { useEffect, useState } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then((res) => res.json())
.then(setUser);
}, []); // empty array = run once after mount
if (!user) return <p>Loading...</p>;
return <h1>{user.name}</h1>;
}
Equivalent of componentDidUpdate
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then((res) => res.json())
.then(setUser);
}, [userId]); // re-runs whenever userId changes
if (!user) return <p>Loading...</p>;
return <h1>{user.name}</h1>;
}
Equivalent of componentWillUnmount (Cleanup)
function LiveTicker() {
const [time, setTime] = useState(new Date().toLocaleTimeString());
useEffect(() => {
const id = setInterval(() => {
setTime(new Date().toLocaleTimeString());
}, 1000);
return () => clearInterval(id); // cleanup on unmount
}, []);
return <p>Current time: {time}</p>;
}
Multiple useEffect Calls
You can (and should) split unrelated side effects into separate useEffect calls rather than cramming everything into one:
function Dashboard({ userId, theme }) {
useEffect(() => {
document.title = `Dashboard — User ${userId}`;
}, [userId]);
useEffect(() => {
document.body.classList.toggle('dark', theme === 'dark');
return () => document.body.classList.remove('dark');
}, [theme]);
}
Chapter 5: Putting It Together
Here is a complete functional component that demonstrates mounting, updating, and unmounting lifecycle patterns side-by-side:
import { useState, useEffect } from 'react';
function PostViewer({ postId }) {
const [post, setPost] = useState(null);
const [viewCount, setViewCount] = useState(0);
// Fetch post data whenever postId changes (mount + update)
useEffect(() => {
let cancelled = false;
fetch(`/api/posts/${postId}`)
.then((res) => res.json())
.then((data) => {
if (!cancelled) setPost(data);
});
return () => {
cancelled = true; // prevent state update on unmounted component
};
}, [postId]);
// Track view count on a timer — clean up on unmount
useEffect(() => {
const id = setInterval(() => {
setViewCount((c) => c + 1);
}, 5000);
return () => clearInterval(id);
}, []);
if (!post) return <p>Loading post...</p>;
return (
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
<small>Views this session: {viewCount}</small>
</article>
);
}
The cancelled flag pattern prevents a React warning ("Can't perform a state update on an unmounted component") when an async fetch resolves after the component has already unmounted.
🧪 Try It Yourself
Task: Build a CountdownTimer component that counts down from 10 to 0, then displays "Time's up!" and stops the interval.
Success criteria: You should see the counter tick down in the browser every second and stop cleanly at 0 without any console warnings.
Starter snippet:
import { useState, useEffect } from 'react';
function CountdownTimer() {
const [count, setCount] = useState(10);
useEffect(() => {
if (count === 0) return; // TODO: handle the done state
const id = setInterval(() => {
setCount((c) => c - 1);
}, 1000);
return () => clearInterval(id); // cleanup
}, [count]);
return <h2>{count > 0 ? `${count}...` : "Time's up!"}</h2>;
}
export default CountdownTimer;
Mount this component in App.jsx and watch it run. Then try adding a "Restart" button that resets count to 10.
🔍 Checkpoint Quiz
Q1. In what order does React call class component lifecycle methods during the mounting phase?
A) render → constructor → componentDidMount
B) constructor → getDerivedStateFromProps → render → componentDidMount
C) componentDidMount → constructor → render
D) constructor → render → getDerivedStateFromProps → componentDidMount
Q2. What does this useEffect do?
useEffect(() => {
document.title = `Hello, ${name}`;
}, [name]);
A) Updates the page title once when the component mounts, then never again
B) Updates the page title after every render regardless of what changed
C) Updates the page title after mount and every time name changes
D) Causes an infinite re-render loop
Q3. Given this class component, what happens when the component unmounts?
class Poller extends React.Component {
componentDidMount() {
this.timerId = setInterval(this.poll, 3000);
}
poll = () => fetch('/api/data').then(console.log);
render() { return <div />; }
}
A) The interval is automatically cleared by React
B) The interval keeps running and fires fetch every 3 seconds forever — a memory leak
C) React throws an error because componentWillUnmount is missing
D) The interval pauses and resumes when the component remounts
Q4. How would you use useEffect to log a message only once when a component first appears on screen — and not on any subsequent re-render?
A1. B — React calls constructor first, then getDerivedStateFromProps (to sync props into state), then render to produce JSX, and finally componentDidMount once the DOM node exists.
A2. C — The dependency array [name] tells React to re-run the effect after mount and whenever name changes. It will not run on other state or prop changes.
A3. B — Because componentWillUnmount is never defined, clearInterval is never called, so the interval keeps firing even after the component is removed from the DOM. This leaks memory and can cause state-update warnings.
A4. Pass an empty dependency array: useEffect(() => { console.log('mounted'); }, []). The empty array means "no dependencies ever change," so React only runs the effect once after the initial mount.
🪞 Recap
- React components go through three lifecycle phases: mounting, updating, and unmounting.
- Class components use named methods (
componentDidMount,componentDidUpdate,componentWillUnmount, etc.) to hook into each phase. useEffectin functional components covers all three phases: the effect body handles mount/update, and the returned cleanup function handles unmount.- The
useEffectdependency array controls when the effect re-runs — empty array for mount-only, specific values for targeted updates. componentDidCatchandgetDerivedStateFromErrorenable error boundaries that catch crashes in the component subtree.
📚 Further Reading
- React Docs — Component Lifecycle — official reference for every class lifecycle method
- React Docs — useEffect — complete guide to the functional equivalent, including cleanup and dependency rules
- React Docs — Error Boundaries — how to isolate crashes with
componentDidCatch - ⬅️ Previous: React Components
- ➡️ Next: State and Props