Introduction
In almost every real-world React application, you need to communicate with a backend server. Whether you are fetching user data, submitting a form, or loading products from a database, API calls are at the core of how modern web applications work.
But handling API calls properly is not just about fetching data—it’s about managing loading states, handling errors, optimizing performance, and writing clean, maintainable code.
In this article, we will explore how to handle API calls in React using both Fetch API and Axios, understand when to use each, and see how this works in real-world applications.
Understanding API Calls in React
At a basic level, an API call is a request sent from your frontend (React app) to a backend server, and the server responds with data.
For example:
Fetch user details
Submit login form
Load product list
In React, API calls are usually handled inside lifecycle methods or hooks like useEffect, because they involve asynchronous operations.
Using Fetch API in React
Fetch API is a built-in browser feature, so you don’t need to install anything.
Let’s start with a simple example.
import { 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((err) => {
setError(err.message);
setLoading(false);
});
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
export default Users;
What’s happening here:
useEffectruns after component mountsAPI call is made
Data is stored in state
UI updates automatically
Using Async/Await with Fetch
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 (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
This version is easier to read and maintain, especially in larger applications.
Using Axios in React
Axios is a popular third-party library that simplifies API calls.
First, install Axios:
npm install axios
Now let’s see the same example using Axios.
import axios from "axios";
import { useEffect, useState } from "react";
function Users() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
axios
.get("https://jsonplaceholder.typicode.com/users")
.then((response) => {
setUsers(response.data);
setLoading(false);
})
.catch((err) => {
setError(err.message);
setLoading(false);
});
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
export default Users;
Axios with Async/Await
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get("https://jsonplaceholder.typicode.com/users");
setUsers(response.data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, []);

Join the conversation! Your thoughts help the community grow.