Introduction to React Component Lifecycle
React applications are built using components, and these components follow a defined journey from creation to destruction. This journey is called the component lifecycle. Understanding the lifecycle is crucial because it gives developers control over what happens at specific points in a component’s existence. By tapping into lifecycle methods (in class components) or hooks (in functional components), developers can manage side effects, handle data fetching, clean up resources, and optimize rendering.
This post introduces the React component lifecycle, explains its phases, explores class-based lifecycle methods, demonstrates how the lifecycle is managed with hooks in functional components, and covers real-world use cases with examples and best practices.
What is a Component Lifecycle?
The component lifecycle refers to the series of events that happen when a React component is:
- Created and inserted into the DOM (Mounting phase).
- Updated with new data or props (Updating phase).
- Removed from the DOM (Unmounting phase).
Each phase provides opportunities to execute specific code by using lifecycle methods or hooks.
Phases of the Component Lifecycle
React components go through three main phases:
- Mounting – The component is created and added to the DOM.
- Updating – The component is re-rendered due to changes in props or state.
- Unmounting – The component is removed from the DOM.
Lifecycle in Class Components vs Functional Components
- In class components, React provides built-in lifecycle methods such as
componentDidMount,componentDidUpdate, andcomponentWillUnmount. - In functional components, lifecycle is managed using React Hooks like
useEffect.
Understanding both is important, though modern React encourages functional components with hooks.
Mounting Phase in Detail
The mounting phase occurs when a component is created and inserted into the DOM.
Class Component Lifecycle Methods During Mounting
- constructor()
- Called before the component mounts.
- Used to initialize state and bind event handlers.
class App extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } render() { return <h1>Count: {this.state.count}</h1>; } } - render()
- Responsible for returning JSX to render the UI.
- Must be pure (no side effects).
- componentDidMount()
- Invoked immediately after the component is mounted.
- Ideal for API calls, subscriptions, or DOM manipulations.
class App extends React.Component { componentDidMount() { console.log("Component mounted!"); } render() { return <h1>Hello World</h1>; } }
Updating Phase in Detail
The updating phase happens when props or state change, causing the component to re-render.
Class Component Lifecycle Methods During Updating
- shouldComponentUpdate(nextProps, nextState)
- Determines whether the component should re-render.
- Helps optimize performance by preventing unnecessary renders.
shouldComponentUpdate(nextProps, nextState) { return nextState.count !== this.state.count; } - render()
- Called again to update the UI with new data.
- componentDidUpdate(prevProps, prevState)
- Invoked after the component updates.
- Useful for reacting to prop/state changes (e.g., making API calls when props change).
componentDidUpdate(prevProps, prevState) { if (prevState.count !== this.state.count) { console.log("Count updated!"); } }
Unmounting Phase in Detail
The unmounting phase occurs when the component is removed from the DOM.
Class Component Lifecycle Method During Unmounting
- componentWillUnmount()
- Invoked just before the component is destroyed.
- Used to clean up side effects like timers, event listeners, or subscriptions.
class App extends React.Component { componentWillUnmount() { console.log("Component will unmount!"); } render() { return <h1>Goodbye</h1>; } }
Functional Components and Lifecycle with Hooks
Modern React primarily uses functional components with hooks instead of class-based lifecycle methods.
The useEffect hook is the most important tool for handling lifecycle in functional components.
Example of Mounting with useEffect
import React, { useEffect } from "react";
function App() {
useEffect(() => {
console.log("Component mounted!");
}, []);
return <h1>Hello World</h1>;
}
The empty dependency array [] ensures this effect runs only once (on mount).
Example of Updating with useEffect
import React, { useState, useEffect } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("Count updated:", count);
}, [count]);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Here, the effect runs every time count changes.
Example of Unmounting with useEffect Cleanup
import React, { useEffect } from "react";
function Timer() {
useEffect(() => {
const timer = setInterval(() => {
console.log("Running timer...");
}, 1000);
return () => {
clearInterval(timer);
console.log("Timer cleared on unmount");
};
}, []);
return <h1>Timer Running</h1>;
}
The function returned from useEffect acts as cleanup and is executed during unmounting.
Lifecycle Diagram Overview
Although we cannot use images here, imagine the following flow:
- Mounting: constructor → render → componentDidMount
- Updating: shouldComponentUpdate → render → componentDidUpdate
- Unmounting: componentWillUnmount
In functional components, all these phases can be managed by carefully using useEffect.
Common Use Cases of Lifecycle Methods and Hooks
1. Fetching Data from APIs
import React, { useEffect, useState } from "react";
function Users() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((res) => res.json())
.then((data) => setUsers(data));
}, []);
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
2. Subscribing and Unsubscribing to Events
import React, { useEffect } from "react";
function WindowResize() {
useEffect(() => {
const handleResize = () => {
console.log("Window resized:", window.innerWidth);
};
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
return <h1>Resize the window to see effect</h1>;
}
3. Controlling Performance with shouldComponentUpdate
class ExpensiveComponent extends React.Component {
shouldComponentUpdate(nextProps) {
return nextProps.value !== this.props.value;
}
render() {
console.log("Rendering ExpensiveComponent");
return <h1>{this.props.value}</h1>;
}
}
4. Cleaning Up Timers
function Clock() {
const [time, setTime] = React.useState(new Date());
useEffect(() => {
const timer = setInterval(() => setTime(new Date()), 1000);
return () => clearInterval(timer);
}, []);
return <h1>{time.toLocaleTimeString()}</h1>;
}
Best Practices for Working with Lifecycle
- Keep Side Effects Inside Lifecycle Methods/Hooks
- Avoid placing side effects inside
render. UseuseEffectorcomponentDidMountinstead.
- Avoid placing side effects inside
- Clean Up Subscriptions and Timers
- Always return cleanup functions in
useEffectto prevent memory leaks.
- Always return cleanup functions in
- Minimize Re-Renders
- Use
shouldComponentUpdate(class) orReact.memo(functional) to prevent unnecessary renders.
- Use
- Group Related Logic in Effects
- Avoid mixing unrelated concerns inside a single
useEffect.
- Avoid mixing unrelated concerns inside a single
- Prefer Functional Components with Hooks
- Modern React applications should prefer functional components for better readability and future-proofing.
Lifecycle Differences Between Class and Functional Components
| Phase | Class Component Method | Functional Component (Hook) |
|---|---|---|
| Mounting | constructor, componentDidMount | useEffect with [] |
| Updating | shouldComponentUpdate, componentDidUpdate | useEffect with [dependencies] |
| Unmounting | componentWillUnmount | useEffect cleanup return function |
Real-World Example: Chat App with Lifecycle
function ChatRoom() {
useEffect(() => {
const connect = () => console.log("Connected to chat server");
const disconnect = () => console.log("Disconnected from chat server");
connect();
return () => {
disconnect();
};
}, []);
return <h1>Chat Room</h1>;
}
This simulates a real-world scenario where connections must be established on mount and cleaned up on unmount.
Leave a Reply