Introduction
In modern web development, most applications depend on external data sources such as APIs. Whether you're building a dashboard, blog, e-commerce site, or admin panel, fetching data from an API is a core skill every React developer must understand.
React provides powerful tools like useState and useEffect hooks that make it easy to fetch and display API data in a clean and efficient way. In this article, we will learn step-by-step how to fetch API data in React, store it in state, and display it on the UI.
This guide is written in simple and practical language, making it easy for beginners to understand while still being useful for experienced developers.
What is an API?
An API (Application Programming Interface) allows different software systems to communicate with each other. In simple terms, an API lets your React application request data from a server and receive it in a structured format, usually JSON.
For example:
Fetching user data from a server
Getting product details for an e-commerce app
Loading blog posts dynamically
Understanding useState in React
The useState hook is used to store and manage data inside a React component.
When working with APIs, we use useState to:
Store fetched data
Track loading state
Handle errors
Example:
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
Explanation:
data → stores API response
loading → tracks whether data is being fetched
error → stores any error that occurs
Understanding useEffect in React
The useEffect hook is used to perform side effects in React components. Fetching data from an API is considered a side effect.
useEffect runs when the component is rendered.
Example:
useEffect(() => {
// API call here
}, []);
The empty dependency array [] ensures the API is called only once when the component loads.
Step-by-Step: Fetch API Data in React
Let’s build a simple example where we fetch user data from a public API and display it.
Step 1: Create a React Component
import React, { useEffect, useState } from "react";
function Users() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
return <div></div>;
}
export default Users;
Step 2: Fetch Data Using useEffect
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => {
if (!response.ok) {
throw new Error("Failed to fetch data");
}
return response.json();
})
.then((data) => {
setUsers(data);
setLoading(false);
})
.catch((error) => {
setError(error.message);
setLoading(false);
});
}, []);
Explanation:
fetch() → makes API request
response.json() → converts response to JSON
setUsers() → saves data
setLoading(false) → stops loading state
Step 3: Display the Data in UI
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<div>
<h2>User List</h2>
<ul>
{users.map((user) => (
<li key={user.id}>
{user.name} - {user.email}
</li>
))}
</ul>
</div>
);
Explanation:
Complete Example Code
import React, { useEffect, useState } from "react";
function Users() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => {
if (!response.ok) {
throw new Error("Failed to fetch data");
}
return response.json();
})
.then((data) => {
setUsers(data);
setLoading(false);
})
.catch((error) => {
setError(error.message);
setLoading(false);
});
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<div>
<h2>User List</h2>
<ul>
{users.map((user) => (
<li key={user.id}>
{user.name} - {user.email}
</li>
))}
</ul>
</div>
);
}
export default Users;
Using Async/Await (Recommended Approach)
Using async/await makes the code cleaner and easier to read.
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
if (!response.ok) {
throw new Error("Failed to fetch data");
}
const data = await response.json();
setUsers(data);
} catch (error) {
setError(error.message);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
Best Practices for Fetching API Data in React
Always handle loading state
Users should know when data is being loaded to improve user experience.
Handle errors properly
Display meaningful error messages instead of crashing the app.
Use async/await for better readability
It makes your code easier to maintain and debug.
Avoid unnecessary re-renders
Use dependency arrays correctly in useEffect.
Separate API logic
Keep API calls in a separate file for cleaner code in large projects.
Common Mistakes to Avoid
Forgetting dependency array in useEffect
Not handling errors
Updating state after component unmount
Calling API multiple times unnecessarily
Real-World Use Cases
Dashboard applications fetching analytics data
E-commerce websites loading products
Social media apps displaying posts
Admin panels showing user details
Conclusion
Fetching and displaying API data in React using useState and useEffect is one of the most important skills for modern frontend development.
By understanding how to manage state, handle side effects, and display dynamic data, you can build powerful and scalable React applications.
Start with simple examples like this, then move to advanced concepts like custom hooks, caching, and state management libraries.