diceline-chartmagnifiermouse-upquestion-marktwitter-whiteTwitter_Logo_Blue

Today I Learned

Axios Request and Response Interceptors

Axios interceptors come in handy when we need to track, register, or work with:

  1. Requests before leaving
  2. Responses before arriving
  3. Both 1 and 2

Thanks to interceptors, we can pass our own handlers/callbacks for the following cases:

  1. Before launching a request
  2. Catching an error at HTTP request launch
  3. Arrival of a response
  4. Catching an error at response arrival

This is an example of how we can use them:

// handlers for the request launch and for catching request launch error
axios.interceptors.request.use(
   (config) => {
      console.log('We are now preparing to launch the request!')
      return config;
   },
   (error) => Promise.reject(error),
);

// handlers for response interceptor and error response interceptor
axios.interceptors.response.use(
     (response) => {
        console.log('We received the response!');
        return response;
     },
     (error) => {
       if (error.response.status === 403) 
         return Promise.reject(error);
     }
},

Each Axios Instance can have its custom configuration and request and response interceptors. This can be very useful when the architecture of our application enforces/allows each of our services to use their own Axios Instance. This way, each service can have an Axios Instance with custom configuration and custom request/response interceptors.

This is how an Axios Instance factory utility function could look like in TypeScript:

export const createAxiosWithInterceptors = (
  requestConfig: AxiosRequestConfig = {},
  requestInterceptorHandlers: Partial<RequestInterceptorHandlers> = DefaultRequestInterceptor,
  responseInterceptorHandlers: Partial<ResponseInterceptorHandlers> = DefaultResponseInterceptor,
) => {
  const axiosInstance = axios.create(requestConfig);

  axiosInstance.interceptors.request.use(
    requestInterceptorHandlers.requestConfigHandler,
    requestInterceptorHandlers.requestErrorHandler,
  );

  axiosInstance.interceptors.response.use(
    responseInterceptorHandlers.responseHandler,
    responseInterceptorHandlers.responseErrorHandler,
  );

  return axiosInstance;
};

where we can define our types and default interceptors as follows:

type RequestConfigHandler = (config: AxiosRequestConfig) => AxiosRequestConfig;
type RequestErrorHandler = (error: AxiosError) => Promise<AxiosError>;
type RequestInterceptorHandlers = {
  requestConfigHandler: RequestConfigHandler;
  requestErrorHandler: RequestErrorHandler;
};

type ResponseHandler = (response: AxiosResponse) => AxiosResponse;
type ResponseErrorHandler = (error: AxiosError) => Promise<AxiosError>;

type ResponseInterceptorHandlers = {
  responseHandler: ResponseHandler;
  responseErrorHandler: ResponseErrorHandler;
};

const DefaultResponseInterceptor = {
  responseHandler: (response: AxiosResponse) => response,
  responseErrorHandler: (error: AxiosError) => Promise.reject(error),
};

const DefaultRequestInterceptor = {
  requestConfigHandler: (config: AxiosRequestConfig) => config,
  requestErrorHandler: (error: AxiosError) => Promise.reject(error),
};

In each of our services we can then use our factory method as follows:

// axios instance with custom request config received through constructor, custom request interceptor and default response interceptor
protected http: AxiosInstance = createAxiosWithInterceptors(
    this.requestConfig,
    {
      requestConfigHandler: (config) => {
        console.log();
        return config;
      },
    },
);

How to extend interfaces declared in external libraries in Typescript

Typescript allows us to easily extend types by using module augumentation.

Let's take a look at one quick example - extending the React Material Ui Library Theme.

All we need to do is create a file ending in .d.ts at the root of our Typescript project - in this case I'll name it material-ui.d.ts:

import {
  Theme as MuiTheme,
} from '@mui/material/styles';

declare module '@mui/material/styles' {
  export interface Theme extends MuiTheme {
    customization?: Record<string, string>;
  }
}

How to declare path aliases in Typescript

Defining path aliases using webpack can save you a lot of headache when it comes to imports, but you must also let Typescript know about them.

Following my previous post on declaring path aliases using webpack, you can configure your tsconfig.json file to in order to be able to use those aliases in Typescript like so:

{
  ...
  "paths": {
    "@/*": ["./src/*"],
    "images/*": ["./assets/images/*"],
  },
  "include": [
    ...
  ],
}

Of course, all paths for defined aliases must be reachable by Typescript. You can check this post out if you are not sure how to do that.

Otherwise, we are going to get this error:

Cannot find module 'images/[your module]' or its corresponding type declarations.

How to import images in Typescript

Normally, when Typescript cannot find something we get this error:

Cannot find module [your module] or its corresponding type declarations ts(2307)

Now let's see how we can fix this, with a simple example. Given the following folder structure:

│── src
│   ├── resources
│   │   ├── ts
│   │   │   ├── **/*.ts
│   │   ├── images
│   │   │   ├── logo.svg
├── ...
├── tsconfig.json

In order to be able to achieve something like this:

import Logo from '[path]/images/logo.svg'

without any ** Typescript errors**, we need to follow these steps:

1.Make sure that the images folder is "reachable" by Typescript.

Adding this inside tsconfig.json will do the trick:

{
  "include": [
        ...
        "resources/**/*.ts",
   ],
}

A configuration like this one will not do:

{
  "include": [
      "resources/ts/**/*.ts",
   ],
}

Because images/ is not included in ts/ and we don't have any other folders declared, so Typescript can't "reach" it.

2.Let Typescript know about the .svg type in that folder.

In the root of the images folder create a file called index.d.ts:

...
├── resources
│   ├── ts
│   │   ├── **/*.ts
│   ├── images
│   │   ├── logo.svg
│   │   ├── index.d.ts
...

With the following contents:

declare module '*.svg' {
  const value: any;
  export = value;
}

Now you should be good to go.