Loading lessons...
Data Fetching with useEffect
Data Fetching with useEffect
Fetching data from API endpoints when components mount is one of the most common applications of useEffect.
Standard Data Fetching Pattern
A robust data-fetching component typically manages three states:
- Data State: Holds response data from the API.
- Loading State: Indicates whether the fetch request is currently in-flight.
- Error State: Captures network failure or HTTP errors.
import { useState, useEffect } from 'react';
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// Define async function inside effect callback
async function loadUsers() {
try {
setLoading(true);
const response = await fetch('https://jsonplaceholder.typicode.com/users');
if (!response.ok) {
throw new Error("HTTP error! status: " + response.status);
}
const data = await response.json();
setUsers(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
loadUsers();
}, []); // Run once on component mount
if (loading) return <p>Loading users...</p>;
if (error) return <p>Error loading users: {error}</p>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name} ({user.email})</li>
))}
</ul>
);
}
⚠️ DocHero Safety Disclaimer
Do NOT make the useEffect argument callback function directly async (useEffect(async () => ...)). useEffect callbacks must return either a cleanup function or nothing. Instead, declare an async function inside the effect callback and invoke it.
Handling Race Conditions with AbortController
If a dependency changes rapidly, responses from earlier network requests can return out of order. Standard browser AbortController can cancel pending fetch calls on cleanup.
useEffect(() => {
const controller = new AbortController();
fetch("/api/search?q=" + query, { signal: controller.signal })
.then(res => res.json())
.then(data => setData(data))
.catch(err => {
if (err.name !== 'AbortError') setError(err);
});
return () => controller.abort(); // Cancel request if query changes before fetch completes
}, [query]);
Summary & TL;DR
- Manage loading, error, and data states during API calls.
- Declare async functions inside the effect callback; never make the top-level effect callback
async. - Use
AbortControllerin the cleanup function to handle race conditions and cancel pending requests.