Axios HOC for React/React Native Apps - Code Enhancements

One common thing in most web apps is API calling. So often the first thing I usually end up making in any web app is a request wrapper.

Why is it needed and how it helps -

  1. Prevent writing repetitive code.
  2. Enforcing code structure by defining the API cases only once.
  3. Adding multiple enhancements in future like performance monitoring by firebase.
  4. Getting environment based URL as well as Authentication tokens.
  5. Handle basic error codes and show errors to user from 1 place.

Prerequisites

  1. To create the wrapper we'd be using axios and qs for querystring parsing and stringifying.
    Run this command to install them - npm install axios && npm i qs

  2. Setting up baseURL for API in .env file like this
    REACT_APP_API_HOST=https://api.example.io/v1

Usage in redux files

It's generally good practice to define URL's as constants module specific in one file as well.

import { DoRequest, REQUEST_TYPE } from "../../helpers/doRequest";

let response = await DoRequest({
  method: REQUEST_TYPE.POST,
  url: '/login',
  data: {username, password}
});

API Wrapper Code

import axios from "axios";
import qs from "qs";
import { useDispatch } from "react-redux";
import { logout } from "../../store/authReducer";

export const REQUEST_TYPE = {
  POST: "post",
  GET: "get",
  PUT: "put",
  DELETE: "delete",
};

export const DoRequest = async (requestData) => {
  const dispatch = useDispatch();
  const token = localStorage.getItem("token"); //Authorization token to use for API's
  const defaultHeader = {
    Authorization: token ? `Token ${token}` : null,
    Accept: "*/*", //NOTE: Default Accept value sometimes gives error 406 on some clients
  };

  const {
    headers = {},
    method = "get", //Default initialization method type
    url = "", //Default URL to hit
    baseURL = process.env.REACT_APP_API_HOST, //Base url ex - in .env file
    params = {}, //Default extra params
    data = {}, //Default data to send
  } = requestData;
  //create request config according to data
  const requestConfig = {
    headers: Object.assign(defaultHeader, headers),
    method,
    url,
    baseURL,
    params,
    paramsSerializer: (params) => {
      return qs.stringify(params, { arrayFormat: "comma" });
    },
    data, //POST request data to send
  };

  try {
    const response = await axios(requestConfig);

    const limit = response.headers["x-collection-range"]; //For pagination
    if (limit) {
      const total = limit?.split("/")[1]; //For pagination purposes
      return { data: response.data, total };
    }

    return { data: response.data }; //Success API response based on API endpoints

  } catch (error) {

    const { response: { status, data } = {} } = error || {}; //Based on ur API endpoints

    if (status === 401) { //Handle API codes as necessary
      dispatch(logout()); // Use hooks to handle Logout or redirection or anything
    } else if (status === 404) {
      window.location.replace("/not-found"); //Redirect to any page via hooks or window object.
    } else {
      alert(`${data.message}`); //Show notifications or alerts from 1 place instead of repetitive code
    }
    return data;
  }
};

Extra axios integrations

Also adding additional performance metric tools like Firebase Performance Monitoring with Axios can also be easily setup with this.

14