The loading anti-pattern.

tehaisperlis

Hazmi Irfan

Posted on November 17, 2020

The loading anti-pattern.

The loading anti pattern is something that I have experience when I need to fetch a data and then display it.

Normally when you want to display a data from an API, there are 5 condition that you want to fulfill.

  1. Show initial data. It could be a blank screen.
  2. Show loading indicator.
  3. Show the result.
  4. Show different message if the result is empty.
  5. Show an error if there is one.

So let's try to built this.

First the data structure. Most likely it would look like this

const data = {
  items: [],
  isLoading: false,
}
Enter fullscreen mode Exit fullscreen mode

The items is a list I want to display and isLoading is a boolean so that I know whether it is loading or not.

So lets try to display either the loading or the list component first.

<List>
 {isLoading ? <Loading/> : <Items items={items} />}
</List>
Enter fullscreen mode Exit fullscreen mode

So far so good. Now we need to differentiate between the result that have a list and a result that return an empty list. Normally I would do it like this.

<Items>
  {items.length > 0 ? items.map(item => <Item>{item.name}</Item>) : <Typography>List is empty</Typography}
<Items/>
Enter fullscreen mode Exit fullscreen mode

Notice that I use items as an indicator on whether the result from an API is empty or not.

This can be a problem because it will show List is empty initially even when we haven't fetch the data yet.

One way to solve this is to just set isLoading to true on the initial data

const data = {
  items: [],
  isLoading: true,
}
Enter fullscreen mode Exit fullscreen mode

Now let's try to handle a case where an API returns an error. First we need to add extra value to the data.

const data = {
  items: [],
  isLoading: true,
  isFailure: false,
}
Enter fullscreen mode Exit fullscreen mode

Now I can use isFailure as an indicator to show the error message.

<Box>
 {!isFailure ? <List/> : <Error/>}
</Box>
Enter fullscreen mode Exit fullscreen mode

Putting everything together, you have something that looks like this

const data = {
  items: [],
  isLoading: true,
  isFailure: false,
}

const Page = ({data}) => 
(
<Box>
 {!data.isFailure ? <List data={data} /> : <Error/>}
</Box>
)

const List = ({data}) => 
(
<Box>
 {data.isLoading ? <Loading/> : <Items items={data.items} />}
</Box>
)

const Items = ({items}) => 
(
<Box>
  {items.length > 0 ? items.map(item => <Item>{item.name}</Item>) : <Typography>List is empty</Typography}
<Box/>
)
Enter fullscreen mode Exit fullscreen mode

So now that I handle all the condition. You might be wondering what is the problem?

Well, the problem is that I'm trying to describe the state of the data using 3 different value. items, isLoading and is isFailure and this makes your render logic more complex than it should be.

I have nested if to cater between different state of the data.

 !isFailure ? isLoading ? items.length > 0
Enter fullscreen mode Exit fullscreen mode

There can also be an invalid state where both isLoading and isFailure can be true.

The problem lies with trying to use boolean to describe the state of the data. Boolean can only represent 2 state of the data but we know now that the data can have 4 state. Initial, loading, failure and success. This is why you end up with so many value.

So how do we fix this?

I was looking at a video about Elm and one of the talk is about this anti pattern and how to solve them. You can view it here.

Basically, what you should do is to have a single value to represent all the possible state of your data. He suggested the state to be notAsked, loading, failure and success.

So now you can describe your data like this

const notAsked = 'notAsked'
const loading = 'loading'
const failure = 'failure'
const success = 'success'

const data = {
  items: [],
  state: notAsked,
}
Enter fullscreen mode Exit fullscreen mode

This pattern solve a couple of problems.

  1. They can no longer be an invalid state.
  2. There is a single value to represent the state.

This can make your render logic a lot more simple.

switch (data.state) {
 case notAsked:
  <Inital/>
  break
 case loading:
  <Loading/>
  break
 case success:
  <List/>
  break
 case failure:
  <Error/>
  break
}
Enter fullscreen mode Exit fullscreen mode

If you don't want to watch a video, you can also read his post How Elm slays a UI antipattern.

Although it is geared towards Elm but I believe it can be implemented elsewhere.

💖 💪 🙅 🚩
tehaisperlis
Hazmi Irfan

Posted on November 17, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related