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.