Fetching data from an API is a fundamental part of web development. In this guide, we’ll explore how to fetch data efficiently using React, handle loading states, manage errors, and prevent race conditions.
📌 Initial Setup
To begin, create a new React application in your code editor. For demonstration purposes, we’ll use the JSONPlaceholder API to fetch posts.
const BASE_URL = "https://jsonplaceholder.typicode.com";
When we hit the /posts
endpoint, the API returns data in the following format:
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit..."
}
🏗 Creating State and Interface
Since we’re using TypeScript (which is highly recommended 😜), let’s define an interface for our posts:
interface Post {
id: number;
title: string;
}
Next, create a state to store the fetched posts:
const [posts, setPosts] = useState<Post[]>([]);
🔄 Fetching Data with useEffect
To fetch data, we’ll use the useEffect
hook, ensuring the request runs once when the component mounts.
useEffect(() => {
const fetchData = async () => {
const response = await fetch(`${BASE_URL}/posts`);
const posts = (await response.json()) as Post[];
setPosts(posts);
};
fetchData();
}, []);
📝 Key Points:
- The
fetch
API retrieves data from the endpoint. - The response is converted to JSON.
- The
useEffect
dependency array is empty, ensuring the function runs only on mount. - The fetched posts are stored in state.
🎨 Rendering Posts
Using the map
function, we’ll display the posts in a list:
return (
<div className="App">
<h1>Data Fetching in React</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.id}. {post.title}</li>
))}
</ul>
</div>
);
🔑 Always provide a unique
key
when rendering lists in React to avoid rendering issues.
⏳ Adding a Loading State
Fetching data takes time, so we should display a loading indicator while waiting for the response.
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
const response = await fetch(`${BASE_URL}/posts`);
const posts = (await response.json()) as Post[];
setPosts(posts);
setIsLoading(false);
};
fetchData();
}, []);
Now, update the UI to show a loading message:
return (
<div className="App">
<h1>Data Fetching in React</h1>
{isLoading ? <div>Loading...</div> : (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.id}. {post.title}</li>
))}
</ul>
)}
</div>
);
⚠ Handling Errors
Errors can occur due to network issues or API failures. Let’s handle errors using a state variable and a try-catch
block.
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
try {
const response = await fetch(`${BASE_URL}/posts`);
if (!response.ok) throw new Error("Failed to fetch data");
const posts = (await response.json()) as Post[];
setPosts(posts);
} catch (error: any) {
setError(error.message);
} finally {
setIsLoading(false);
}
};
fetchData();
}, []);
Modify the UI to display errors:
if (error) {
return <div>Error: {error}</div>;
}
🏎 Handling Race Conditions
Race conditions occur when multiple API calls return in an unexpected order. To prevent this, we use AbortController
to cancel previous requests.
const [page, setPage] = useState(1);
const abortControllerRef = useRef<AbortController | null>(null);
useEffect(() => {
const fetchData = async () => {
abortControllerRef.current?.abort(); // Cancel previous request
abortControllerRef.current = new AbortController();
setIsLoading(true);
try {
const response = await fetch(`${BASE_URL}/posts?page=${page}`, {
signal: abortControllerRef.current.signal,
});
const posts = (await response.json()) as Post[];
setPosts(posts);
} catch (error: any) {
if (error.name !== "AbortError") {
setError(error.message);
}
} finally {
setIsLoading(false);
}
};
fetchData();
}, [page]);
📌 Adding Pagination
We’ll add a button to update the page number, triggering a new API request:
<button onClick={() => setPage((prev) => prev + 1)}>
Next Page ({page})
</button>
🎉 Conclusion
And that’s it! 🚀 You’ve learned how to fetch data efficiently in React, manage loading states, handle errors, and prevent race conditions.
You can explore the full working example on CodeSandbox. Happy coding! 😃