Escape Loading State Hell In Redux

Marek Pukaj
JavaScript in Plain English
5 min readNov 26, 2020

--

Photo by Alexandar Todov on Unsplash

As a React developer working with API, you probably have to handle the loading and error states of your requests. When working on smaller applications, dealing with loading states is not a big issue. However, with the larger apps things can get complicated quickly. Sooner or later your Redux store might become loading states hell, which can be insanely hard to maintain. After working on several large projects, I came up with a robust loading and error states handling mechanism that might be useful for you.

The simplest approach to the loading and error states

When you design your reducers, and you want to handle loading states, chances are you end up with code like this.

This code is pretty simple. There are just three actions. One dispatched usually at the beginning of the request, and another two dispatched when the request finishes. Either successfully or with an error. The problem with this approach starts to appear when you want to handle multiple request actions within the same reducer. To demonstrate this problem, let’s go ahead and let’s try to add more actions to our reducer.

Can you see the problem with this code? Imagine a scenario where get todos request is started, and action GET_TODOS_STARTED is dispatched. Loading state is set to true and our UI probably displays a loading spinner. Now imagine that this request takes some time to finish. During this time, you want to add another todo to the list, so you start create todo request, and action CREATE_TODO_STARTED will be dispatched, and UI will show another loading spinner. Now we have two loading spinners that depend on a single loading state, which is controlled by the two requests. If just one of them finishes, both loading spinners disappear, and our UI will be in an inconsistent state. As I’ve already mentioned the problem is the loading state, shared among multiple requests. A possible solution to this problem is to create a new loading state for each request. By doing so, your code can look like this.

As you’ve probably noticed, we can’t go far with this approach. With more and more request actions to handle, our reducer will become bloated by the loading states. To deal with this problem, we have to come up with a more robust solution.

A more robust solution to loading and error states hell

We know that we want a better solution, and ideally, we don’t want to pollute our reducers with the loading state code. The first thing we have to do is to create a separate reducer that will manage loading stuff. As we already know, we want a separate state for each request so, naturally, we can store these loading states in the array. This array will represent all active requests in our application. In this case, an active request is a request, which is either in a loading state or in an error state. If the request finishes with an error, we consider this request as the active one and store it in the array until we process this error. The request handling reducer might look like this.

Looking at this code, you might notice that the actions, we handle in this reducer are very similar to those that we saw in the first version of TodoReducer. Their names are just more generalized. Their handling will be also pretty similar. We don’t have any information about which loading state in the array belongs to which request yet. To solve this issue we can give a unique name to each of our requests in the application. For example, if we have a request GET /api/todos we name it getTodos. We can store all of these request names in a structure similar to enum, and the code can look like this.

Now we have a solution to identify our requests. Let’s revisit our RequestReducer and let’s implement actions handling.

Handling actionREQUEST_FINISHED is pretty straightforward. If a request finishes, we remove this request by its name from the requests array.
When we handle actionREQUEST_STARTED we must check if we have a request with the same name in the active requests array. If we do, we just update this request with the correct loading state, and we clear its error state as well. If the request is not in the array, we just add it there.
Finally, when handling REQUEST_FAILED we want to update the request state in the active request array, so we change its loading state as well as the error state.
Now we have finished the request reducer. However, to use the request state in UI we need selectors that will know how to interpret this reducer state. We need three selectors. One for getting the loading state of a particular request, one for getting the loading state of multiple requests at once, and one for getting the error state. Here are our three selectors.

Integrate loading mechanism to the rest of the application

Now we have working Redux code that can be integrated into a new or an existing application. In the next section, I will show you how to do so.
Let’s start with the helper function, which will handle dispatching request actions.

This function is just an example of handling request actions flow. You can use this helper function, for example, inside your async thunk.

As a next step, we need to implement action creators, so let’s do it.

Finally, let’s use our Redux code in the React component to demonstrate how this loading and error handling mechanism works. In this example, I am using connect API of the react-redux library, but you can use hooks API as well.

Summary

In this tutorial, we created a robust Redux mechanism for the loading and error states handling. We started with a really simple solution, which we refactored to a solution that is way easier to use and expand. If you have any questions or tips on how to improve this solution, please let me know in the comments section. Up to me, this is a really important topic, so I will be glad if you share your tips or solutions as well.

--

--