Web DevelopmentUse Async Actions Hook in ReactJS Web Development

Are you tired of repeatedly writing boilerplate code to handle API requests in your software projects?
Updated at14.03.2024
Published at21.04.2023
Marcin Sadowski
Marcin Sadowski

CTO @ JS and Web3 Expert

Table of contents

  1. Introduction
  2. Common boilerplate code in software projects
  3. React Async
  4. Implementation
  5. Conclusion

Share the article

Introduction

Are you tired of repeatedly writing boilerplate code to handle API requests in your software projects? While managing the response and handling the states of the request may seem trivial, it can quickly become tedious and time-consuming. 

With high-level languages like JavaScript, there are declarative approaches to handling asynchronous operations, but loading states and error handling are often left out. This results in developers having to write state management for these missing parts multiple times across various files. 

In this article, we will explore the benefits of creating a proper utility function to handle these repetitive tasks, saving time and effort in future projects.

Common boilerplate code in software projects

For sure you have been in this situation that you have to handle API request “again” in your software projects. In most situations it is trivial. The only thing related “boilerplate code” is management of response and handling states of the request. That’s something obvious that it needs to be programmed, however, we live in high-level languages like Javascript and this language comes with declarative approaches. Async operation are commonly handled in imperative manner. Declarative code is mostly written for execution of different kind of requests. What missing are loading states and error handling. In the results we have to multiple times write state management for these missing parts in many files. We wouldn't have to do this if we had prepared proper utility function.

React Async

Have you ever heard about React Async? It’s a library designed to make asynchronous calls to your API. The idea is simple. Instead of doing async/await calls, handling loadings, or catching errors, you simply can use hook `useAsync` and it will hande everything for you as it is shown in this example from docs:

import { useAsync } from "react-async"
// You can use async/await or any function that returns a Promise
const loadPlayer = async ({ playerId }, { signal }) => {
const res = await fetch(`/api/players/${playerId}`, { signal })

if (!res.ok) throw new Error(res.statusText)
    return res.json()
}

const MyComponent = () => {
    const { data, error, isPending } = useAsync({ promiseFn: loadPlayer, playerId: 1 })
    if (isPending) return "Loading..."
    if (error) return `Something went wrong: ${error.message}`
    if (data)
        return (
            <div>
                <strong>Player data:</strong>
                <pre>{JSON.stringify(data, null, 2)}</pre>
            </div>
        )
    return null
}

As you can see it’s pretty straightforward. The main issue with that this library is currently left without any support from the community. The last release was in 2020. Almost 3 years ago. In the GitHub repository, we can see that some issues persist since 2019. It makes it a tool that most developers should avoid because of the old codebase. 

Alternatively, we can search for other similar libraries, however, ultimately we can create our own which we can use at the component level!

Implementation

Look at it. It’s simple if we think of potential usage in our projects. We need to handle 3 stages of API calls: pending, fulfilled, and rejected. For every stage, we need to handle proper state updates of this asynchronous operation. We in Mobile Reality have written something similar a long time ago and we didn’t notice that someone has created a library for these cases. Let me show our implementation:

import { useEffect, useState } from 'react';
// our hook
export const useWithAsyncAction = (args) => {
    let argsWithLoading = {};
    let argsWithError = {};
    // async operations agregated in objects
    for (const key of Object.keys(args)) {
        argsWithLoading = {
            ...argsWithLoading,
            [key]: false,
        };
        argsWithError = {
            ...argsWithError,
            [key]: false,
        };
    }

    const [loading, setLoading] = useState(argsWithLoading);
    const [errors, setErrors] = useState(argsWithError);
    const [anyLoading, setAnyLoading] = useState(false);
    const [anyError, setAnyError] = useState('');

    useEffect(() => {
        setAnyLoading(Object.values(loading).some(Boolean));
        setAnyError(Object.values(errors).find(Boolean));
    }, [loading, errors]);

    const setLoadingState = (key, value) => {
        setLoading({
            ...argsWithLoading,
            [key]: value,
        });
    };

    const setErrorState = (key, value) => {
        setErrors({
            ...argsWithError,
            [key]: value,
        });
    };

    for (const key of Object.keys(args)) {
        argsWithExecute = {
            ...argsWithExecute,
            [key]: async (data, param, additionalParam) => {
                try {
                    setLoadingState(key, true);
                    const response = await args[key](
                        data,
                        param,
                        additionalParam,
                    );
                    setLoadingState(key, false);
                    setErrorState(key, false);
                    return response;
                } catch (err) {
                    setLoadingState(key, false);
                    const errorData = err?.response?.data || {
                        message: err?.response,
                    };
                    setErrorState(key, errorData);
                    try {
                        if (!err.response)
                            err.response = {
                                data: {
                                    message: myErrorMessage
                                }
                            };
                        if (!err.response.data) err.response.data = {};
                        if (!err.response.data.message)
                            err.response.data.message = myErrorDataMessage;
                    } catch (err) {
                        // handle catched error
                        console.log(err);
                    }
                }
            },
        };
    }

    return {
        actions: argsWithExecute,
        loading,
        anyLoading,
        errors,
        anyError
    };
};

As you see it’s a very simple implementation for REST async call written in our React app. We can maintain it with ease and use it in our react component. For API clients we use mostly Axios so this hook is designed for this tool and its API response. Of course, this function has been changed multiple times already as sometimes we need something more complex. What it might be?

  1. Different HTTP clients like fetch API or superagent. 

  2. Promise cancelation and more operations on promise object

  3. More extensive error handling 

  4. Optional callback function

  5. More extensive configuration with middleware like redux-thunk

  6. Make it more global with Redux state and handle partially it with action and reducer 

  7. GraphQL API. For starting the project this utility does its work. 

Conclusion

By reading these 7 points above you might already know that making it more complex it’s a lot of work to adjust and that’s true. Managing every case in react application and react hooks is complicated and time-consuming. We are familiar with it as we have created our library “just” for selection input in React Native. It’s just select and this library includes 665 files. In this case, we can have a similar situation if we would like to build a bulletproof solution.

Bibliography:

Did you like the article?Find out how we can help you.

Matt Sadowski

CEO of Mobile Reality

CEO of Mobile Reality

Related articles

Discover essential React JS Best Practices for Frontend Development Teams. Elevate your coding game with expert tips and techniques.

25.04.2024

Top ReactJS Best Practices for Frontend Teams

Discover essential React JS Best Practices for Frontend Development Teams. Elevate your coding game with expert tips and techniques.

Read full article

Discover the essential guide for CTOs comparing GO vs Node JS. Make the right choice for your tech stack. Get insights now! #node #nodejs #go #golang #CTO

25.04.2024

GO vs Node JS : A Complete Comparison for CTOs

Discover the essential guide for CTOs comparing GO vs Node JS. Make the right choice for your tech stack. Get insights now! #node #nodejs #go #golang #CTO

Read full article

Discover the essential guide for CTOs comparing Node JS vs PHP. Make the right choice for your tech stack. Get insights now! #nodejs #php #CTO

25.04.2024

Node JS vs PHP: A Complete Comparison for CTOs

Discover the essential guide for CTOs comparing Node JS vs PHP. Make the right choice for your tech stack. Get insights now! #nodejs #php #CTO

Read full article