Learnitweb

Interceptors in a React Application

1. Introduction

In any real-world React application, you’ll likely need to make network requests to a backend server. Often, you want to intercept these requests and responses to:

  • Inject authentication headers (like JWT tokens)
  • Show or hide a loading spinner during network activity
  • Log all API requests and responses
  • Handle common errors globally (like redirecting on a 401 Unauthorized)
  • Retry failed requests

This is where interceptors come in.

Interceptors are functions that sit between your application and the actual network call. They allow you to hook into Axios’s request/response lifecycle and modify the request before it’s sent or the response before it’s processed.

Axios provides two types of interceptors:

  • Request Interceptors – executed before the request is sent.
  • Response Interceptors – executed after a response is received (or an error occurs).

This allows you to perform cross-cutting operations (like showing a loader or handling auth) in one central place, instead of duplicating logic in every component.

2. Implementation

Step 1: Install Axios

npm install axios

Step 2: Create a Centralized Axios Instance

We’ll create a custom Axios instance so we can attach interceptors and reuse it everywhere in the app.

src/api/axiosInstance.js

import axios from 'axios';

const axiosInstance = axios.create({
  baseURL: 'https://jsonplaceholder.typicode.com',
  timeout: 10000, // 10 seconds timeout
});

export default axiosInstance;

Step 3: Add Request & Response Interceptors

Add global interceptors to the instance you just created. Here’s where we can:

  • Attach auth headers
  • Trigger a global loader
  • Handle common errors like 401, 403, 500
  • Log activity
let loaderSetter = null; // external function to update loader state

export const registerLoaderSetter = (fn) => {
  loaderSetter = fn;
};

axiosInstance.interceptors.request.use(
  (config) => {
    console.log(`[Request] ${config.method.toUpperCase()} ${config.url}`);

    // Simulate adding an auth token
    const token = localStorage.getItem('authToken');
    if (token) {
      config.headers['Authorization'] = `Bearer ${token}`;
    }

    if (loaderSetter) loaderSetter(true); // start loader
    return config;
  },
  (error) => {
    if (loaderSetter) loaderSetter(false); // stop loader
    console.error('[Request Error]', error);
    return Promise.reject(error);
  }
);

axiosInstance.interceptors.response.use(
  (response) => {
    console.log(`[Response] ${response.config.url}`, response.status);
    if (loaderSetter) loaderSetter(false); // stop loader
    return response;
  },
  (error) => {
    if (loaderSetter) loaderSetter(false); // stop loader
    console.error('[Response Error]', error);

    // Global error handling
    if (error.response?.status === 401) {
      alert('Unauthorized. Redirecting to login...');
      window.location.href = '/login';
    }

    if (error.response?.status === 500) {
      alert('Something went wrong on the server.');
    }

    return Promise.reject(error);
  }
);

Step 4: Create Loader Context for Global State

We’ll use React Context to track loading status across the app.

src/context/LoaderContext.jsx

import { createContext, useContext, useState } from 'react';

const LoaderContext = createContext();

export const LoaderProvider = ({ children }) => {
  const [loading, setLoading] = useState(false);
  return (
    <LoaderContext.Provider value={{ loading, setLoading }}>
      {children}
    </LoaderContext.Provider>
  );
};

export const useLoader = () => useContext(LoaderContext);

Step 5: Setup App with Loader and Axios Integration

src/App.jsx

import { useEffect } from 'react';
import { LoaderProvider, useLoader } from './context/LoaderContext';
import { registerLoaderSetter } from './api/axiosInstance';
import Demo from './Demo';

const Loader = () => {
  const { loading } = useLoader();

  return loading ? (
    <div style={{ background: '#eee', padding: 10, position: 'fixed', top: 0, left: 0, width: '100%', textAlign: 'center' }}>
      🔄 Loading...
    </div>
  ) : null;
};

const AppWithLoader = () => {
  const { setLoading } = useLoader();

  useEffect(() => {
    registerLoaderSetter(setLoading); // connect loader with interceptors
  }, [setLoading]);

  return (
    <>
      <Loader />
      <Demo />
    </>
  );
};

const App = () => (
  <LoaderProvider>
    <AppWithLoader />
  </LoaderProvider>
);

export default App;

Step 6: Make a Test Request in a Component

import axiosInstance from './api/axiosInstance';

const Demo = () => {
  const callApi = async () => {
    try {
      const response = await axiosInstance.get('/posts/1');
      alert(`Title: ${response.data.title}`);
    } catch (error) {
      console.error('API call failed');
    }
  };

  return (
    <div style={{ padding: 20 }}>
      <h2>Test API Call with Interceptors</h2>
      <button onClick={callApi}>Fetch Post</button>
    </div>
  );
};

export default Demo;

3. Best Practices

  • Avoid duplicating interceptors inside components. Always attach them once globally.
  • Use axios.create() to build a custom instance and reuse it everywhere.
  • Don’t forget to cancel or clean up interceptors if you ever attach them dynamically in a component (using eject()).
  • Track concurrent requests for more advanced loaders (so quick responses don’t flicker the loader).
  • Log important metadata such as status codes, headers, or trace IDs if needed.