React Lifecycle Explained — Component Mount, Update, and Unmount
In this tutorial, you will learn about React Lifecycle Explained. We cover key concepts, practical examples, and best practices to help you master this topic.
React component lifecycle describes the stages a component goes through from creation to destruction: mounting (birth), updating (growth), and unmounting (death).
What You'll Learn
- The three lifecycle phases: mount, update, unmount
- How useEffect maps to lifecycle events
- How to run code on mount (initialization)
- How to run code on update (reacting to changes)
- How to clean up on unmount
Why It Matters
Understanding the lifecycle helps you place code in the right phase. Fetching data on mount, validating on update, and cleaning up on unmount prevents bugs and memory leaks.
Real-World Use
Durga Antivirus Pro's real-time scanner starts a Websocket connection on mount, reconnects on network changes (update), and closes the connection on unmount to prevent resource leaks.
flowchart LR
A[Mount] --> B[Render]
B --> C[useEffect Mount]
C --> D[Update]
D --> E[Re-render]
E --> F[useEffect Cleanup]
F --> G[useEffect Update]
G --> H[Unmount]
H --> I[useEffect Cleanup]
style A fill:#3b82f6,color:#fff
Mounting Phase
Mounting is when the component first appears in the DOM:
import { useState, useEffect } from "react";
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
// Runs once on mount (when userId changes)
useEffect(() => {
console.log("Component mounted. Fetching user:", userId);
const controller = new AbortController();
async function fetchUser() {
try {
setLoading(true);
const response = await fetch(`/api/users/${userId}`, {
signal: controller.signal
});
const data = await response.json();
setUser(data);
setLoading(false);
} catch (err) {
if (err.name !== "AbortError") {
console.error("Failed to fetch user:", err);
setLoading(false);
}
}
}
fetchUser();
// Cleanup function runs on unmount or before next effect
return () => {
console.log("Cleanup: aborting fetch");
controller.abort();
};
}, [userId]); // Re-runs when userId changes
if (loading) return <div>Loading user...</div>;
if (!user) return <div>User not found</div>;
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
Expected output: Fetching user data on mount, canceling the request on unmount, and re-fetching when userId changes.
The useEffect with [userId] dependency runs on mount and whenever userId changes. The cleanup function runs before the next effect and on unmount. This replaces componentDidMount, componentDidUpdate, and componentWillUnmount from class components.
Updating Phase
The updating phase happens when props or state change:
function SearchResults({ query }) {
const [results, setResults] = useState([]);
const [searchHistory, setSearchHistory] = useState([]);
// Effect for search - runs when query changes
useEffect(() => {
if (!query) return;
const fetchResults = async () => {
const response = await fetch(`/api/search?q=${query}`);
const data = await response.json();
setResults(data);
};
fetchResults();
}, [query]);
// Effect for logging - runs when results change
useEffect(() => {
if (results.length > 0) {
console.log(`Search returned ${results.length} results`);
}
}, [results]);
// Effect without dependencies - runs on every render
useEffect(() => {
console.log("Component rendered");
// Avoid: this runs on every render, including state changes
}); // No dependency array
return (
<div>
{results.map(item => (
<div key={item.id}>{item.title}</div>
))}
</div>
);
}
Expected output: Search results update when query changes. The log shows result count when results update. The render log fires on every render.
Understanding dependencies is critical. An empty array [] runs once. No array runs on every render. The array tells React when to re-run the effect.
Unmounting Phase
Clean up resources when the component leaves the DOM:
import { useState, useEffect } from "react";
function LiveClock() {
const [time, setTime] = useState(new Date());
useEffect(() => {
console.log("Clock started");
const interval = setInterval(() => {
setTime(new Date());
}, 1000);
// Cleanup function
return () => {
console.log("Clock stopped");
clearInterval(interval);
};
}, []); // Empty array = run once on mount
return <p>Current time: {time.toLocaleTimeString()}</p>;
}
function ParentComponent() {
const [showClock, setShowClock] = useState(true);
return (
<div>
<button onClick={() => setShowClock(!showClock)}>
{showClock ? "Hide" : "Show"} Clock
</button>
{showClock && <LiveClock />}
</div>
);
}
Expected output: Clicking "Hide Clock" destroys the LiveClock component. The cleanup function clears the interval, preventing memory leaks.
Every useEffect that creates a subscription, timer, or event listener must return a cleanup function. Without it, the resource lives on after the component is gone.
Strict Mode Double-Mounting
In development with StrictMode, effects run twice:
import { StrictMode } from "react";
function DevEffect() {
useEffect(() => {
console.log("Effect runs");
return () => console.log("Cleanup runs");
}, []);
// In StrictMode dev: Cleanup -> Effect -> Cleanup -> Effect
// This helps find missing cleanup functions
return <p>Check console</p>;
}
// In index.js
root.render(
<StrictMode>
<App />
</StrictMode>
);
Expected output: In development, you see "Effect runs", "Cleanup runs", "Effect runs", "Cleanup runs" in sequence.
StrictMode intentionally double-invokes effects in development to detect bugs: missing cleanup functions, incorrect dependency arrays, and impure effects. This does not happen in production.
Class Component Lifecycle
Legacy class lifecycle methods and their useEffect equivalents:
// Class component
class UserPage extends React.Component {
componentDidMount() {
// On mount
fetchUser(this.props.userId);
}
componentDidUpdate(prevProps) {
// On update, when userId changed
if (prevProps.userId !== this.props.userId) {
fetchUser(this.props.userId);
}
}
componentWillUnmount() {
// On unmount
cleanup();
}
render() { return <div>...</div>; }
}
// Equivalent with hooks
function UserPage({ userId }) {
useEffect(() => {
fetchUser(userId);
return () => cleanup();
}, [userId]);
}
Expected output: The hook version is more concise. A single useEffect with [userId] replaces componentDidMount, componentDidUpdate, and componentWillUnmount.
Hooks simplify lifecycle management. Instead of splitting logic across three methods, you co-locate related code in one effect. The dependency array makes the intent explicit.
Common Mistakes
Missing dependency arrays — Forgetting the array causes infinite re-renders. Always specify dependencies.
Incorrect dependencies — Using stale variables in effects without listing them causes bugs. The linter (
react-hooks/exhaustive-deps) catches this.Not cleaning up subscriptions — Intervals, WebSockets, and event listeners leak memory if not cleaned up in the effect's return function.
Fetching data without abort — If the component unmounts before fetch completes,
setStateon unmounted component causes warnings. UseAbortController.Putting too much in one effect — Split unrelated logic into separate useEffect calls. Data fetching and logging should not share an effect.
Practice Questions
What are the three lifecycle phases? Mounting (component appears), updating (props/state change), unmounting (component is removed).
How do you run code only on mount?
useEffect(() => { ... }, [])with an empty dependency array.How do you run code when a specific prop changes? Include the prop in the dependency array:
useEffect(() => { ... }, [propName]).What does the cleanup function in useEffect do? It runs before the component unmounts and before the effect re-runs. Use it to cancel subscriptions, timers, or fetch requests.
Why does StrictMode double-fire effects? To help detect missing cleanup functions and impure effects during development.
Challenge
Build a ConnectionStatus component that monitors online/offline status. Use useEffect to add online/offline event listeners on mount and clean them up on unmount. Show a banner when offline. Simulate the effect by temporarily going offline in DevTools.
FAQ
Mini Project
Build a WebSocketChat component that connects to a WebSocket on mount, reconnects with exponential backoff on connection loss (update phase), and disconnects on unmount. Display messages as they arrive. Show connection status indicator. Use useEffect with cleanup for all lifecycle phases. Test by toggling the component visibility.
What's Next
Continue with effects and refs:
React Effects, React Refs, React State
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro