How to Make React JS API Calls Using Axios: A Complete Guide with Source Code Analysis

Axios is a promise-based HTTP client that enables React applications to make HTTP requests using async/await patterns, with built-in support for request cancellation, interceptors, and error handling through its core architecture in the axios/axios repository.

When building React applications that communicate with backend services, implementing consistent React JS API calls using axios provides a robust alternative to the native fetch API. The axios/axios repository exposes a flexible instance-based architecture defined in lib/axios.js and lib/core/Axios.js that allows developers to configure base URLs, headers, and timeout settings while maintaining clean component code.

Installing Axios in a React Project

Start by adding the package to your React project:

npm install axios

Making a Simple GET Request in React Components

The most common pattern for React JS API calls using axios involves the useEffect hook combined with the AbortController API to prevent memory leaks when components unmount.

In lib/cancel/AbortController.ts, Axios implements cancellation support following the standard DOM AbortController specification. This allows you to pass a signal to any request:

import React, { useEffect, useState } from 'react';
import axios from 'axios';

function UsersList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController();

    axios
      .get('https://jsonplaceholder.typicode.com/users', {
        signal: controller.signal,
      })
      .then((response) => {
        setUsers(response.data);
        setLoading(false);
      })
      .catch((err) => {
        if (axios.isCancel(err)) {
          console.log('Request canceled');
        } else {
          setError(err);
          setLoading(false);
        }
      });

    return () => controller.abort();
  }, []);

  if (loading) return <p>Loading…</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <ul>
      {users.map((u) => (
        <li key={u.id}>{u.name}</li>
      ))}
    </ul>
  );
}

Creating a Pre-Configured Axios Instance

Rather than importing the default export everywhere, create a dedicated client using axios.create() defined in lib/axios.js. This factory method returns an instance that inherits all methods from the default Axios export while allowing custom configuration:

// src/apiClient.js
import axios from 'axios';

const apiClient = axios.create({
  baseURL: 'https://api.myapp.com/v1',
  timeout: 5000,
  headers: { 'Content-Type': 'application/json' },
});

export default apiClient;

Adding Request Interceptors for Authentication

In lib/core/InterceptorManager.js, Axios implements a queue-like structure for chaining functions that run before requests dispatch. Use apiClient.interceptors.request.use() to inject authentication tokens:

apiClient.interceptors.request.use((config) => {
  const token = localStorage.getItem('authToken');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

When consuming this in React components, import the configured instance:

import React, { useEffect, useState } from 'react';
import apiClient from '../apiClient';

function Profile() {
  const [profile, setProfile] = useState(null);
  
  useEffect(() => {
    const ctrl = new AbortController();
    
    apiClient
      .get('/me', { signal: ctrl.signal })
      .then((res) => setProfile(res.data))
      .catch((err) => console.error(err));
      
    return () => ctrl.abort();
  }, []);
  
  return profile ? <div>{profile.name}</div> : <p>Loading…</p>;
}

Implementing a Custom useAxios Hook

Encapsulate React JS API calls using axios within a reusable hook to manage loading states, error handling, and cancellation across components:

// src/hooks/useAxios.js
import { useEffect, useState } from 'react';
import axios from 'axios';

export default function useAxios(config, immediate = true) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(immediate);
  const [error, setError] = useState(null);

  useEffect(() => {
    if (!immediate) return;

    const controller = new AbortController();
    setLoading(true);

    axios({ ...config, signal: controller.signal })
      .then((res) => setData(res.data))
      .catch((err) => setError(err))
      .finally(() => setLoading(false));

    return () => controller.abort();
  }, [JSON.stringify(config), immediate]);

  return { data, loading, error };
}

Usage within components:

import React from 'react';
import useAxios from '../hooks/useAxios';

function TodoList() {
  const { data: todos, loading, error } = useAxios(
    { url: '/todos', method: 'GET' },
    true
  );

  if (loading) return <p>Loading…</p>;
  if (error) return <p>{error.message}</p>;
  
  return (
    <ul>
      {todos.map((t) => (
        <li key={t.id}>{t.title}</li>
      ))}
    </ul>
  );
}

Global Error Handling with Response Interceptors

The response interceptor pipeline, managed through lib/core/Axios.js, processes all responses before they reach your component code. Implement global error handling by attaching to interceptors.response.use():

apiClient.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      window.location = '/login';
    }
    return Promise.reject(error);
  }
);

Understanding Axios Internal Architecture

When executing React JS API calls using axios, the library processes requests through a specific lifecycle defined in lib/core/Axios.js:

  1. Configuration merge – Request-specific config merges with instance defaults
  2. Request interceptors – Functions registered in lib/core/InterceptorManager.js execute in sequence
  3. Adapter dispatch – The browser uses the XHR adapter to open an XMLHttpRequest
  4. Response interceptors – Functions process the adapter's return value in reverse order
  5. Promise resolution – Returns a normalized response object with data, status, headers, and config

If any step throws, Axios rejects the promise with an error object containing response, request, and config properties for debugging.

Summary

  • Axios instances created via axios.create() in lib/axios.js provide pre-configured HTTP clients with shared defaults for React applications.
  • Cancellation tokens using AbortController prevent state updates on unmounted components when making React JS API calls using axios.
  • Interceptors defined in lib/core/InterceptorManager.js enable centralized authentication injection and global error handling.
  • Custom hooks like useAxios encapsulate loading states, error boundaries, and cleanup logic for maintainable data fetching patterns.
  • Error objects returned by Axios include full request/response context, allowing granular error recovery in React components.

Frequently Asked Questions

How do I handle POST requests when making React JS API calls using axios?

Use axios.post() or your configured instance's .post() method, passing the endpoint as the first argument and the payload as the second. The method returns a promise that resolves with the server response, which you can handle with async/await or .then() chains inside React event handlers or useEffect hooks.

Why should I use axios instead of fetch for React API calls?

Axios provides automatic JSON transformation, request/response interceptors for global logic, built-in timeout and cancellation support through AbortController, and consistent error handling that rejects promises for HTTP error statuses. These features require manual implementation when using the native fetch API.

How do I cancel an axios request when a React component unmounts?

Create an AbortController instance inside useEffect, pass its signal property in the axios config, and return a cleanup function that calls controller.abort(). This triggers cancellation logic in lib/cancel/AbortController.ts and prevents state updates after unmounting.

Where is the axios request lifecycle implemented in the source code?

The core request dispatch and interceptor orchestration reside in lib/core/Axios.js, which coordinates configuration merging, interceptor execution via lib/core/InterceptorManager.js, and adapter dispatch. Response normalization occurs before the final promise resolution returns control to your React component.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →