Optimizing Axios Performance in React

Optimizing Axios in React applications is essential for improving page load times, reducing server load, and ensuring a smooth user experience. This article provides a practical guide on how to streamline your Axios configuration through custom instances, request interceptors, cancellation tokens, response caching, and integration with modern state management tools.

1. Create a Custom Axios Instance

Instead of importing the global Axios library across multiple files, create a reusable, customized instance. This centralizes your configuration, making it easier to manage base URLs, timeouts, and headers.

// api.js
import axios from 'axios';

const api = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 10000, // 10 seconds timeout
  headers: {
    'Content-Type': 'application/json',
  },
});

export default api;

2. Utilize Request and Response Interceptors

Interceptors allow you to run your code or modify requests and responses before they are handled by then or catch. This is ideal for automatically attaching authorization tokens or globally handling HTTP errors.

// Attaching authorization token automatically
api.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => Promise.reject(error)
);

// Global error handling
api.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response && error.response.status === 401) {
      // Redirect to login or refresh token
    }
    return Promise.reject(error);
  }
);

3. Implement Request Cancellation

To prevent memory leaks and unnecessary network usage, cancel pending requests when a component unmounts or when a dependency changes. Axios supports this using the native AbortController.

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

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

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

    api.get(`/users/${userId}`, { signal: controller.signal })
      .then((res) => setUser(res.data))
      .catch((err) => {
        if (axios.isCancel(err)) {
          console.log('Request canceled');
        } else {
          // Handle actual error
        }
      });

    // Cancel the request if the component unmounts or userId changes
    return () => {
      controller.abort();
    };
  }, [userId]);

  return <div>{user ? user.name : 'Loading...'}</div>;
}

4. Implement Response Caching

Repeatedly fetching the exact same data drains bandwidth. You can implement a simple in-memory cache to store API responses and serve them instantly on subsequent requests.

const cache = new Map();

export const getCachedData = async (url) => {
  if (cache.has(url)) {
    return cache.get(url);
  }
  const response = await api.get(url);
  cache.set(url, response.data);
  return response.data;
};

5. Leverage TanStack Query (React Query)

The most robust way to optimize Axios in React is by combining it with a data-fetching library like TanStack Query (formerly React Query). It handles caching, background updates, deduplication of requests, and garbage collection automatically.

import { useQuery } from '@tanstack/react-query';
import api from './api';

const fetchUserData = async (userId) => {
  const { data } = await api.get(`/users/${userId}`);
  return data;
};

function UserComponent({ userId }) {
  const { data, error, isLoading } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUserData(userId),
    staleTime: 5 * 60 * 1000, // Keep data fresh for 5 minutes
  });

  if (isLoading) return <span>Loading...</span>;
  if (error) return <span>Error: {error.message}</span>;

  return <div>{data.name}</div>;
}