Enhancing React Performance with Interceptors and Middleware

Omkar ManteOmkar Mante
3 min read

Introduction

Optimizing performance and eliminating repetitive code are critical for developing scalable React apps. One of the most effective approaches is to use interceptors and middleware to handle API calls, authentication, error handling, and logging centrally. In this blog post, we'll look at how to use Axios interceptors with Redux Toolkit Middleware to optimize network requests and state management in React apps.

Understanding Interceptors in React

Interceptors allow you to modify requests and responses globally before they are handled by the .then() or .catch() block.

Why Use Interceptors?

  • Centralized API request handling

  • Automatic token addition for authentication

  • Global error handling and logging

  • Response transformation

  • Request retry mechanism for transient failures

Setting Up Axios Interceptors

Installing Axios

First, install Axios if you haven’t already:

npm install axios

Creating an Axios Instance with Interceptors

Create a utility file to configure Axios interceptors:

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

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

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

// Response Interceptor
apiClient.interceptors.response.use(
  (response) => response,
  async (error) => {
    if (error.response) {
      if (error.response.status === 401) {
        localStorage.removeItem('authToken');
        window.location.href = '/login';
      } else if (error.response.status >= 500) {
        console.error('Server error:', error.response);
      }
    }
    return Promise.reject(error);
  }
);

export default apiClient;

Using Axios Interceptors in Components

Now, import and use apiClient in your React components:

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

const UserProfile = () => {
  const [user, setUser] = useState(null);

  useEffect(() => {
    apiClient.get('/user/profile')
      .then(response => setUser(response.data))
      .catch(error => console.error('Error fetching profile:', error));
  }, []);

  return user ? <div>Welcome, {user.name}!</div> : <div>Loading...</div>;
};

export default UserProfile;

Implementing Middleware in React

Middleware in Redux acts as a bridge between the action and the reducer. It is useful for logging, authentication, API calls, caching, and performance optimization.

Why Use Middleware?

  • Centralized API request logic

  • Logging and debugging

  • Error handling and monitoring

  • Performance optimizations (throttling, caching, debouncing)

  • Request deduplication to prevent redundant API calls

Setting Up Middleware in Redux Toolkit

Installing Redux Toolkit

npm install @reduxjs/toolkit react-redux

Creating Custom Middleware for Logging and API Handling

// src/redux/middleware/loggerMiddleware.js
const loggerMiddleware = (store) => (next) => (action) => {
  console.log('Dispatching:', action);
  const result = next(action);
  console.log('Next State:', store.getState());
  return result;
};

export default loggerMiddleware;

Creating API Middleware for Handling Requests and Caching

// src/redux/middleware/apiMiddleware.js
const apiMiddleware = (store) => (next) => async (action) => {
  if (action.type !== 'api/call') return next(action);

  const { url, method, data, onSuccess, onError } = action.payload;

  try {
    const response = await fetch(url, {
      method,
      headers: { 'Content-Type': 'application/json' },
      body: data ? JSON.stringify(data) : undefined,
    });

    if (!response.ok) throw new Error('API call failed');

    const responseData = await response.json();
    store.dispatch({ type: onSuccess, payload: responseData });
  } catch (error) {
    store.dispatch({ type: onError, payload: error.message });
  }
};

export default apiMiddleware;

Applying Middleware in the Redux Store

// src/redux/store.js
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './slices/userSlice';
import loggerMiddleware from './middleware/loggerMiddleware';
import apiMiddleware from './middleware/apiMiddleware';

const store = configureStore({
  reducer: { user: userReducer },
  middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(loggerMiddleware, apiMiddleware),
});

export default store;

Conclusion

By implementing Axios interceptors and Redux middleware, we achieve: ✅ Performance Optimization - Reducing redundant API calls, caching responses, and centralizing logic. ✅ Code Reduction - Avoiding boilerplate authentication and error-handling code. ✅ Better Maintainability - Keeping API and state management logic clean and reusable. ✅ Improved Debugging - Enhanced logging and monitoring of actions.

Interceptors and middleware are essential tools in modern React development, allowing developers to enhance efficiency and optimize performance seamlessly. Start implementing them today and see the improvements in your project! 🚀

1
Subscribe to my newsletter

Read articles from Omkar Mante directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Omkar Mante
Omkar Mante