React  

How to Handle API Calls in React Using Axios or Fetch API?

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:

  • useEffect runs after component mounts

  • API 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();
}, []);

Fetch vs Axios

FeatureFetch APIAxios
InstallationNot requiredRequired
JSON HandlingManual (response.json())Automatic
Error HandlingManualEasier
Request InterceptorsNot availableAvailable
Timeout SupportNo built-inYes
Browser SupportNativeRequires package

Real-World Example: Form Submission

const handleSubmit = async () => {
  try {
    const response = await axios.post("/api/login", {
      email: "[email protected]",
      password: "123456",
    });

    console.log(response.data);
  } catch (error) {
    console.error(error);
  }
};

This is commonly used in:

  • Login systems

  • Registration forms

  • Payment processing

Handling Loading, Error, and Empty States

A well-designed React app always handles these states:

  • Loading → Show spinner or message

  • Error → Show user-friendly message

  • Empty → Show “No data available” message

This improves user experience significantly.

When to Use Fetch vs Axios

Use Fetch when:

  • You want no external dependency

  • Project is small or simple

Use Axios when:

  • You need interceptors (auth tokens)

  • You handle complex APIs

  • You want cleaner syntax and better error handling

Common Mistakes Developers Make

  • Not handling errors properly

  • Not using loading state

  • Calling API multiple times unnecessarily

  • Writing API logic inside components (instead of services)

Best Practice: Create API Service Layer

Instead of calling APIs directly in components, create a separate file:

import axios from "axios";

const api = axios.create({
  baseURL: "https://api.example.com",
});

export const getUsers = () => api.get("/users");

Then use it in components.

This improves:

  • Code readability

  • Reusability

  • Maintainability

Real-World Use Cases

  • E-commerce: Fetch product list

  • Dashboard: Load analytics data

  • Social app: Fetch posts and comments

Conclusion

Handling API calls in React is a fundamental skill for building modern applications. Whether you use Fetch API or Axios, the goal is to manage data efficiently, handle errors gracefully, and provide a smooth user experience.

Axios is generally preferred in larger applications due to its simplicity and powerful features, while Fetch works well for smaller or lightweight projects.

The key is not just making API calls, but structuring them properly so your application remains scalable, maintainable, and performant.