Welcome back to the React Mastery Series!
In the previous article, we explored Forms in React and learned how controlled components help React manage user input.
Today, we will dive into one of the most important and widely used React Hooks:
useEffect Hook
useEffect is a fundamental Hook used in almost every real-world React application.
It helps us perform side effects such as:
- Fetching data from APIs
- Updating browser titles
- Subscribing to events
- Setting timers
- Connecting to WebSockets
- Cleaning up resources
Understanding useEffect is essential for building production-ready React applications.
What Are Side Effects?
Before understanding useEffect, we need to understand what a side effect is.
A side effect is any operation that happens outside the normal component rendering process.
Examples:
API Calls
Component Render
|
↓
Call Backend API
|
↓
Receive Data
|
↓
Update State
Browser Interaction
Examples:
- Changing document title
- Accessing local storage
- Manipulating browser APIs
Subscriptions
Examples:
- WebSocket connections
- Event listeners
- Timers
Why Do We Need useEffect?
React components should ideally be pure functions.
Example:
function UserProfile() {
return <h1>User Profile</h1>;
}
The component receives data and returns UI.
However, real applications need external interactions.
Example:
Render Component
|
↓
Fetch User Data
|
↓
Update UI
React provides useEffect to handle these operations safely.
Basic Syntax of useEffect
useEffect(() => {
// Side effect logic
}, []);
The Hook accepts two parameters:
useEffect(
function,
dependency array
)
First Parameter: Effect Function
This contains the code that should execute.
Example:
useEffect(() => {
console.log("Component rendered");
});
Second Parameter: Dependency Array
The dependency array controls when the effect runs.
Example:
useEffect(() => {
console.log("Runs once");
}, []);
The empty array means:
Run this effect only after the initial render.
Understanding useEffect Execution
Consider:
function App() {
useEffect(() => {
console.log("Effect executed");
}, []);
return <h1>Hello React</h1>;
}
Execution flow:
Component Created
|
↓
Initial Render
|
↓
DOM Updated
|
↓
useEffect Executes
Important:
useEffect runs after the component renders.
useEffect Without Dependency Array
Example:
useEffect(()=>{
console.log("Runs every render");
});
Execution:
Initial Render
↓
Effect Runs
State Update
↓
Render
↓
Effect Runs Again
This happens after every render.
Use carefully.
useEffect With Empty Dependency Array
Example:
useEffect(()=>{
console.log("Runs once");
},[]);
Execution:
Component Mount
|
↓
Effect Executes Once
Common use cases:
- Initial API calls
- Loading configuration
- Setting up subscriptions
useEffect With Dependencies
Example:
useEffect(()=>{
console.log("User changed");
},[userId]);
Now the effect runs when:
userId changes
Example:
setUserId(10);
Flow:
State Update
|
↓
Component Re-render
|
↓
Dependency Changed
|
↓
useEffect Executes
Real-World Example: Fetching API Data
A common React pattern:
import { useEffect, useState } from "react";
function Users() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch("https://api.example.com/users")
.then((response) => response.json())
.then((data) => {
setUsers(data);
});
}, []);
return (
<div>
{users.map((user) => (
<h3 key={user.id}>{user.name}</h3>
))}
</div>
);
}
Flow:
Component Loads
|
↓
useEffect Runs
|
↓
API Request
|
↓
Receive Response
|
↓
Update State
|
↓
Render User List
Cleanup Function in useEffect
Some effects need cleanup.
Examples:
- Removing event listeners
- Clearing timers
- Closing WebSocket connections
Example:
useEffect(() => {
const timer =
setInterval(() => {
console.log("Running");
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
The function returned from useEffect is called Cleanup Function
Component Lifecycle Using useEffect
In class components:
componentDidMount()
componentDidUpdate()
componentWillUnmount()
In functional components:
useEffect handles all lifecycle behavior.
Mounting
Equivalent to:
componentDidMount()
Example:
useEffect(()=>{
loadData();
},[]);
Runs once when component appears.
Updating
Equivalent to:
componentDidUpdate()
Example:
useEffect(()=>{
searchProducts();
},[searchText]);
Runs whenever searchText changes.
Unmounting
Equivalent to:
componentWillUnmount()
Example:
useEffect(() => {
return () => {
disconnect();
};
}, []);
Runs when component is removed.
Real-World Example: WebSocket Connection
Imagine a live banking notification system.
Component loads:
Open Dashboard
|
↓
Create WebSocket Connection
|
↓
Receive Transaction Alerts
When user leaves:
Navigate Away
|
↓
Close WebSocket
Implementation:
useEffect(() => {
const socket =
new WebSocket(
"wss://bank.com/alerts"
);
return () => {
socket.close();
};
}, []);
Dependency Array Mistakes
Mistake 1: Missing Dependencies
Example:
useEffect(()=>{
fetchUser(userId);
},[]);
Problem:
userId is used but not included.
Correct:
useEffect(()=>{
fetchUser(userId);
},[userId]);
Mistake 2: Infinite Loops
Example:
useEffect(()=>{
setCount(count+1);
},[count]);
Flow:
count changes
|
↓
Effect runs
|
↓
setCount()
|
↓
count changes again
|
↓
Infinite Loop
useEffect vs Event Handlers
A common confusion:
Should something happen inside:
- useEffect?
- Button click handler?
Example:
User clicks save button:
<button onClick={saveData}> Save </button>
This belongs in an event handler.
Automatic behavior:
Component Loaded
|
↓
Fetch Data
This belongs in:
useEffect()
useEffect Best Practices
Keep Effects Focused
Avoid:
useEffect(()=>{
fetchData();
updateTitle();
connectSocket();
},[]);
Better:
useEffect(()=>{
fetchData();
},[]);
useEffect(()=>{
connectSocket();
},[]);
Avoid Unnecessary Effects
Do not use effects for simple calculations.
Avoid:
useEffect(()=>{
setFullName( firstName + lastName);
},[firstName,lastName]);
Instead:
const fullName = firstName + lastName;
Always Cleanup Resources
Cleanup:
- Timers
- Subscriptions
- Event listeners
- Connections
Enterprise Example: Banking Dashboard
Consider a retail banking application.
When the dashboard opens:
User Opens Dashboard
|
↓
useEffect Executes
|
↓
Fetch Account Summary API
|
↓
Fetch Transactions API
|
↓
Update State
|
↓
Render Dashboard
When leaving:
User Leaves Page
|
↓
Cleanup Runs
|
↓
Close Connections
This pattern is used in production React applications.
Key Takeaways
Today, we learned:
✅ useEffect handles side effects in React.
✅ Effects run after rendering.
✅ Dependency arrays control execution timing.
✅ Empty dependency arrays run effects once.
✅ Cleanup functions prevent memory leaks.
✅ API calls, subscriptions, and timers commonly use useEffect.
Coming Next 🚀
In Day 14, we will explore:
React Hooks Deep Dive – useRef and useMemo
We will learn:
- What useRef is
- Accessing DOM elements
- Persisting values without re-rendering
- useMemo for performance optimization
- Expensive calculations
- Real-world optimization examples
These Hooks are essential for writing high-performance React applications.
Happy Coding! 🚀
Top comments (0)