How to Fetch Data in React
Use data fetching in React when a component needs API data, database-backed content, or dynamic external resources after the page loads. The most common pattern is useEffect() plus fetch() and component state.
What you’ll build or solve
You’ll learn how to fetch data in React using useEffect, fetch, and useState. You’ll also know how to handle loading and errors cleanly.
Learn React on Mimo
When this approach works best
This approach is the right choice when the component depends on remote data after rendering.
Common real-world scenarios include:
- User dashboards
- Product lists
- Blog feeds
- Search results
- Analytics widgets
This is a bad idea when the framework already supports server-side loaders or React Server Components that fetch more efficiently.
Prerequisites
You only need:
- A React component
- Basic hooks knowledge
- An API endpoint
Step-by-step instructions
Step 1: Store loading data in state
Start with state for the data and loading status.
JavaScript
import { useEffect, useState } from "react";
function Users() {
const [users, setUsers] = useState([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
fetch("https://api.example.com/users")
.then((response) => response.json())
.then((data) => {
setUsers(data);
setIsLoading(false);
});
}, []);
if (isLoading) {
return <p>Loading...</p>;
}
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
export default Users;
The empty dependency array ensures the fetch runs once on mount.
Step 2: Add error handling
Always handle failed requests.
JavaScript
const [error, setError] = useState(null);
useEffect(() => {
fetch("https://api.example.com/users")
.then((response) => response.json())
.then((data) => {
setUsers(data);
setIsLoading(false);
})
.catch(() => {
setError("Failed to load users");
setIsLoading(false);
});
}, []);
This keeps the UI predictable when APIs fail.
Step 3: Use async/await for cleaner effects
Async logic often reads better with an inner function.
JavaScript
useEffect(() => {
async function loadUsers() {
try {
const response = await fetch("https://api.example.com/users");
const data = await response.json();
setUsers(data);
} catch {
setError("Failed to load users");
} finally {
setIsLoading(false);
}
}
loadUsers();
}, []);
What to look for:
- Use
useEffect()for side-effect fetching - Use state for data, loading, and errors
- Empty dependencies run once
- Async/await improves readability
- Always handle failures
Examples you can copy
Product list
JavaScript
useEffect(() => {
fetch("/api/products")
.then((response) => response.json())
.then(setProducts);
}, []);
Search results
JavaScript
useEffect(() => {
fetch(`/api/search?q=${query}`)
Analytics widget
JavaScript
useEffect(() => {
loadMetrics();
}, []);
Common mistakes and how to fix them
Mistake 1: Fetching directly in render
What the reader might do:
JavaScript
const data = fetch("/api/users");
Why it breaks: this runs on every render and causes repeated requests.
Corrected approach:
Move the fetch into useEffect().
Mistake 2: Missing dependency logic
What the reader might do:
Use query inside the fetch but keep [].
Why it breaks: the request does not rerun when the query changes.
Corrected approach:
Add the dependency.
Mistake 3: No loading state
What the reader might do:
Render users.map() immediately.
Why it breaks: the data may not exist yet.
Corrected approach:
Track loading separately.
Troubleshooting
If the fetch loops, inspect the dependency array.
If the UI crashes, add loading and null-safe rendering.
If stale data appears, include the correct dependencies.
If the framework supports loaders, compare server-side alternatives.
Quick recap
- Use
useEffect()for data fetching - Store data, loading, and errors in state
- Use async/await for clarity
- Add correct dependencies
- Never fetch directly in render
Join 35M+ people learning for free on Mimo
4.8 out of 5 across 1M+ reviews
Check us out on Apple AppStore, Google Play Store, and Trustpilot