Loading lessons...
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.
jsx
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>
);
}
Warning
> 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.
jsx
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.