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.
