DEV Community

Cover image for Ratios — a React hook library for managing axios requests, includes cancellation mechanism
abemscac
abemscac

Posted on • Updated on

Ratios — a React hook library for managing axios requests, includes cancellation mechanism

This article is written for Ratios 1.x.

For Ratios 2.x, please check npm or Github for documentation.

Many of us have used axios — a Promise based HTTP client for the browser and NodeJS in React projects. There are a lot of React hook libraries that help us bring axios into our apps, but none of them handle cancel token for you.

So what does a cancel token do? Why is it so important? Please take a look at the following component first:

Example component

This is a component which shows text “Loading…” when an API call is still going on, and display the result after getting the response. Nothing special. You can find the demo on Stackblitz.

This component works great when there is only 1 page in our app. But as you can see, we didn’t get the response immediately — it took a couple seconds before we got the response from server.

So what if we add react-router in our app, and navigate to another component before we get the response?

Well, this happened.

Navigate to another component before the API responds

We got an error message from React, saying that we “Can’t perform a React state update on an unmounted component.” You can find the demo on Stackblitz.

Error we got when we navigate to another component before API responds

Seems familiar, uh? What does this mean? Well, the message is actually very straight to the point, but we still don’t know what is happening. It works great when there’s only 1 page in our app!

If you take a look at the code of User component, you will see something like this:

import React, { useState, useEffect } from "react";
import UserAPI from "../apis/User";

export default function App() {
  const [users, setUsers] = useState({
    isLoading: true,
    data: []
  });

  useEffect(() => {
    const fetchUser = async () => {
      const { data } = await UserAPI.getAll();
      const { data: users } = data;
      setUsers({
        isLoading: false,
        data: users
      });
    };

    fetchUser();
  }, []);

  return (
    <div>
      <h1>Users</h1>
      {users.isLoading ? (
        "Loading..."
      ) : (
        <ol>
          {users.data.map(user => (
            <li key={user.id}>
              {user.last_name} {user.first_name}
            </li>
          ))}
        </ol>
      )}
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

We first declare a state named “users” with { isLoading: true, data: [] }, call an API after the component is mounted, and finally set the response to the “users” state we have declared. Nothing special, just like how everyone else does it. Then what’s the problem?

The problem is that the API call is still going on after the component is unmounted. We only told our app to call an API and update states based on the API response, but we didn’t tell it to cancel the request when the component is unmounted/destroyed.

So if we navigate to Home before the API responds, our app will still trying to execute setUsers logic when it receives the response, which leads to the error we saw above — “Can’t perform a React state update on an unmounted component.”. Because the User component is already unmounted, which means the “users” state no longer exists.

Great, now we finally know what is going on here! If we want to get rid of this problem, we just need to cancel the request when the component unmounts (same logic can be applied to any asynchronous behaviour, promise is just one of them).

The usage of CancelToken has been clearly described in the official documentation of axios, you can go check it out here if you’re curious about how to use it. You can find the working demo with native axios cancellation on Stackblitz.

After applying cancellation mechanism to our app, the strange error is forever gone. Wonderful! But is there any React hook library that does all of the stuff described above for me? Good news, yes, that’s why I made Ratios.

Ratios is a React hook library for managing axios requests, includes cancellation mechanism. It significantly reduce the lines of our codes. Take getUser API we just demonstrated for example:

Code comparison before and after using ratios

The code on the left is doing exactly the same thing as the code on the right, and we have reduced the size of code from about 25 lines to only 3 lines.

It merges these steps into one:

  1. Declare a state with useState hook
  2. Create a new CancelTokenSource with useRef hook
  3. Call an API in an useEffect hook, and trigger the cancel token we got from step 2 in clean-up function
  4. Set API response data to the state we declared in step 1

So the final code will look like this:

import React from "react";
import { useAxiosRequest } from "ratios";
import UserAPI from "../apis/User";

const UserComponent = () => {
  const getUsersRequest = useAxiosRequest(UserAPI.getAll, {
    immediate: true, // Execute API immediately
  });

  return (
    <div>
      {getUsersRequest.isLoading ? "Loading..." :
      JSON.stringify(getUsersRequest.data)}
    </div>
  );
};

export default UserComponent;
Enter fullscreen mode Exit fullscreen mode

You can find the working demo of Ratios on Stackblitz.

As for the library and detailed documentation, you can find it on Github.

GitHub logo abemscac / ratios

A React hook library for managing axios requests, includes cancellation mechanism.

Ratios

A React hook library for managing axios requests, includes cancellation mechanism.

Installation

  • If you're using yarn: yarn add ratios
  • If you're using npm: npm install ratios --save

Demo

See live demo on Stackblitz.

For more information about why we should cancel a request before component unmounts, please see this article.

Basic usage

1. First, manage your axios requests in a proper way

// File: /src/apis/user.js
import axios from "axios";
const instance = axios.create({
  baseURL: "/api/users",
  headers: {
    "Content-Type": "application/json",
  },
  // ...
});

const UserAPI = {
  getAll: (config) => instance.get("", config),
  create: (data) => (config) => instance.post("", data, config),
  updateById: (id, data) => (config)
Enter fullscreen mode Exit fullscreen mode

I hope this article can help you solve your problem, and most importantly, to help you get better. Thank you all.

Top comments (0)