Let’s build a simple React app that fetches and displays user data from an API when the component mounts to learn the useEffect hook in ReactJS.
1. Setting Up the Component
import Reat, { useState, useEffect } from "react";
const UserList = () => {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Fetch data when the component mounts
const fetchUsers = async () => {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const data = await response.json();
setUsers(data); // Update state with user data
} catch (error) {
console.error("Error fetching users:", error);
} finally {
setLoading(false);
}
};
fetchUsers();
}, []); // Empty dependency array -> runs only once on mount
return (
<div>
<h2>User List</h2>
{loading ? <p>Loading...</p> :
<ul>
{users.map(user => (
<li key={user.id}>{user.name} - {user.email}</li>
))}
</ul>
}
</div>
);
};
export default UserList;
How useEffect
Works in This Example
- Runs When Component Mounts:
- The
useEffect
hook executes thefetchUsers
function once when the component is first rendered. - The empty dependency array
[]
ensures that it runs only on the initial render.
- The
- Fetches Data Asynchronously:
fetchUsers
usesasync/await
to call the API and update the state with user data.
- Handles Loading State:
- The
loading
state ensures that a loading message is displayed while fetching data.
- The
2. Using useEffect
with Dependencies
We can also make useEffect
respond to state changes. Let’s create an example where we fetch data when a user selects a different category.
import React, { useState, useEffect } from "react";
const CategoryFetcher = () => {
const [category, setCategory] = useState("posts");
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/${category}`);
const result = await response.json();
setData(result);
} catch (error) {
console.error("Error fetching data:", error);
}
};
fetchData();
}, [category]); // Runs whenever 'category' changes
return (
<div>
<h2>Data for: {category}</h2>
<button onClick={() => setCategory("posts")}>Posts</button>
<button onClick={() => setCategory("users")}>Users</button>
<button onClick={() => setCategory("comments")}>Comments</button>
<ul>
{data.slice(0, 5).map(item => (
<li key={item.id || item.email}>{JSON.stringify(item)}</li>
))}
</ul>
</div>
);
};
export default CategoryFetcher;
Key Takeaways
- The effect re-runs whenever
category
changes ([category]
in dependencies). - Clicking a button updates the
category
state, triggering a new API call.
3. Cleaning Up Effects (Example: Event Listeners)
If your effect involves event listeners, intervals, or subscriptions, cleanup is necessary to avoid memory leaks.
import React, { useState, useEffect } from "react";
const WindowResize = () => {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize); // Cleanup when unmounting
};
}, []); // Runs only on mount and unmount
return <h2>Window Width: {width}px</h2>;
};
export default WindowResize;
Why Use Cleanup?
- Without cleanup, multiple event listeners would be created, leading to performance issues.
- The cleanup function (inside
return
) runs when the component unmounts.
Summary
Use Case | Example |
---|---|
Fetch API Data on Mount | useEffect(() => {...}, []) |
Fetch Data on State Change | useEffect(() => {...}, [dependency]) |
Event Listeners | useEffect(() => { addEventListener(); return () => removeEventListener(); }, []) |
Cleanup on Unmount | Return a function inside useEffect |
Best Practices
- Use an empty dependency array (
[]
) if you only want the effect to run once. - Always cleanup event listeners, intervals, and subscriptions.
- Avoid unnecessary API calls by properly managing dependencies.