Implement global loading and error state with redux, thunk, routine and TypeScript
When dispatching async actions with redux-thunk
, we usually have three actions for each thunk: REQUEST
, SUCCESS
and FAILURE
. Imagine we have a thunk to fetch data, we would dispatch FETCH_DATA_REQUEST
before we send the API request, then dispatch FETCH_DATA_SUCCESS
when the request succeeds or dispatch FETCH_DATA_FAILURE
when the request fails.
To store the loading state, we probably have a isFetching
in our redux state tree as shown in the redux documentation and we change this state when we receive the REQUEST
, SUCCESS
and FAILURE
actions.
If you have followed this way to implement the loading state, you probably already find that it is really tedious to write the boilerplate code again and again for each thunk.
Luckily, we now have a shiny small library called redux-thunk-routine
(https://www.npmjs.com/package/redux-thunk-routine) to remove those boilerplate code that defines and dispatches actions while having better static type check at the same time (when using TypeScript).
But how about the isFetching
state? Do we still need to write those repetitive code in reducer? Let’s have a look at a more elegant solution and take more benefits from using redux-thunk-routine
— action names are defined in a standard pattern you can safely rely on.
For each routine, the library defines three actions by appending standard suffixes to the routine name. Let’s say we implement “fetch data” thunk using redux-thunk-routine
: it will define three actions for by default: FETCH_DATA/REQUEST
, FETCH_DATA/SUCCESS
and FETCH_DATA/FAILURE
.
Assume that all our actions are defined by redux-thunk-routine
, then we can reduce them in a centralized manner to set isFetching
state. Actually, to make the state more flexible, we will declare the loading state as a dictionary-like object — the routine name is the key, and the value(as boolean) indicates whether the routine is loading.
To express it in TypeScript, we write the type definition below:
Then we can write the reducer to catch and handle all actions from the loading state perspective:
To use the state, we have the selectors below:
Note: the selectors above may cause re-render as we are creating selectors dynamically everytime when calling
selectLoading
. We should create selectors that accept arguments instead. See: https://github.com/reduxjs/reselect#q-how-do-i-create-a-selector-that-takes-an-argument
As we can see, the design of the loading state and selectors make it super flexible whenever we want to know if A GIVEN
, ANY
or SOME
routines are loading.
Amazingly, the same principle also applies to the global error state. This time, let’s have a look at the implementation of all related parts in one file (using ducks):
Again, you can find the demo code on GitHub and try the demo app online.
Thanks for reading :)