How to Implement Axios Interceptors in React

Axios interceptors are powerful tools that allow you to intercept and modify HTTP requests or responses before they are handled by then or catch blocks. This article provides a straightforward, step-by-step guide on how to implement Axios interceptors in a React application to globally manage authentication tokens, handle API errors, and streamline your network requests.

Step 1: Create an Axios Instance

Instead of using the global Axios instance, it is best practice to create a custom Axios instance. This keeps your configurations modular and prevents interceptors from leaking into external API calls that do not require them.

Create a file named apiClient.js (or apiClient.ts if using TypeScript):

import axios from 'axios';

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

export default apiClient;

Step 2: Implement the Request Interceptor

Request interceptors are commonly used to inject authorization headers (like JWT tokens) into outgoing requests automatically.

Add the following code to your apiClient.js file:

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

Step 3: Implement the Response Interceptor

Response interceptors are ideal for handling global error codes, such as redirecting a user to the login page if a 401 Unauthorized status is returned, or refreshing expired access tokens.

Add this response interceptor to apiClient.js:

apiClient.interceptors.response.use(
  (response) => {
    // Return the response data directly to simplify component-level code
    return response;
  },
  async (error) => {
    const originalRequest = error.config;

    // Check for a 401 error and ensure we haven't already retried the request
    if (error.response && error.response.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;
      try {
        // Perform token refresh logic here
        const newAccessToken = await refreshAuthToken(); 
        localStorage.setItem('accessToken', newAccessToken);
        
        // Update the authorization header and retry the original request
        originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
        return apiClient(originalRequest);
      } catch (refreshError) {
        // If refresh fails, clear tokens and redirect to login
        localStorage.removeItem('accessToken');
        window.location.href = '/login';
        return Promise.reject(refreshError);
      }
    }

    return Promise.reject(error);
  }
);

// Mock token refresh function
async function refreshAuthToken() {
  const response = await axios.post('https://api.example.com/auth/refresh', {
    refreshToken: localStorage.getItem('refreshToken'),
  });
  return response.data.accessToken;
}

Step 4: Use the Axios Instance in React Components

With the interceptors configured, you can import and use apiClient in your React components. The request headers and error handling will execute transparently.

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

function UserProfile() {
  const [user, setUser] = useState(null);
  const [error, setError] = useState('');

  useEffect(() => {
    apiClient.get('/user/profile')
      .then((response) => {
        setUser(response.data);
      })
      .catch((err) => {
        setError('Failed to load user profile.');
      });
  }, []);

  if (error) return <p>{error}</p>;
  if (!user) return <p>Loading...</p>;

  return (
    <div>
      <h1>Welcome, {user.name}</h1>
    </div>
  );
}

export default UserProfile;