A React package to manage loading and error states when dealing with asynchronous data calls

An article about our AsyncView component, part of an open source React toolbox.

Natan Cieplinski24th September 2021

In modern frontend web applications it is often the case that before the rendering of a component starts, there is some kind of data fetching. It may be through an API call or a local database request. Even if data fetching is performed in the background, giving feedback on its state can go a long way in making the application feel more polished and even more responsive.

There are typically three stages that are involved while fetching asynchronous data: a loading stage, to inform the users that the data is being fetched, an error stage, to inform them that something did go wrong and maybe provide some solution or additional steps to solve their issue, and a successful stage, where we want to render the UI with the loaded data.

Since we encountered this problem many times, we decide to write a package that incorporates the necessary logic of rendering parts of the UI according to the data fetching state. This component is part of a bigger package called react-toolbox that contains tools used frequently in our projects. You can check out our package on npm or GitHub.

Usage

Our component takes 5 props:

  • The data returned by the API call.
  • The error potentially returned by the API call.
  • What we want to render during the loading phase.
  • What we want to render in case of success.
  • What we want to render in case of error.

The component will look at the data and error props. If both are null or undefined, then the renderLoading prop gets rendered. If an error is present, the renderError component gets rendered, otherwise if the data is present, the renderSuccess prop gets rendered.

Here is what a typical usage looks like:

1import React, { useEffect } from 'react'
2import { AsyncView } from '@aboutbits/react-toolbox'
3
4const MyCommponent = () => {
5    const [data, setData] = useState(null)
6    const [error, setError] = useState(null)
7
8    useEffect(() => {
9        fetch('https://jsonplaceholder.typicode.com/todos')
10            .then(response => setData(response.json()))
11            .catch(error => setError(error))
12    }, [])
13
14    return (
15        <AsyncView
16            data={data}
17            error={error}
18            renderLoading={<LoadingTodos items={5}/>}
19            renderSuccess={(data) => {data.map((todo) => <Todo data={todo}/>)}}
20            renderError={(error) => <ErrorScreen>{error.message}</ErrorScreen>}
21        />
22    );
23}
24

The code above will result in the following behaviours (successful fetch on the left, failed one on the right):

But there is more. Internally, we use SWR for our API calls. We think it's a great package that provides us additional functionality like built-in cache, typescript compatibility, and more. Since SWR already manages data and errors, we can rewrite the previous component to this:

1import React, { useEffect } from 'react'
2import { useSWR } from 'swr'
3import { AsyncView } from '@aboutbits/react-toolbox'
4
5const MyCommponent = () => {
6    const { data, error } = useSWR('https://jsonplaceholder.typicode.com/todos/1')
7
8    return (
9      <AsyncView
10        data={data}
11        error={error}
12        renderLoading={<LoadingTodos items={5}/>}
13        renderSuccess={(data) => {data.map((todo) => <Todo data={todo}/>)}}
14        renderError={(error) => <ErrorScreen>{error.message}</ErrorScreen>}
15      />
16    );
17}
18

With the above example, we have a component that manages our API call states, plus all the great features provided by SWR, encapsulated in a simple and readable way.

Check out the GitHub repo if you want to use our package or just read the full source code.

How can we help you?
We are happy to assist you.
Contact us now