Handling loading actions the proper way in Redux
TLDR; Don’t put UI logic into reducers instead put it into a separate reducer.
When handling asynchronous actions in your application, most of the time you want to let users know that their request is being executed in form of some loading indicator.
Adding UI logic to reducers is like mixing apples and pears, and is also against SoC (Separation of Concerns).
Let’s first start with the usual (bad) way of handling loading actions
In our reducer we declare requestInProgress and refreshing property.
So every time we dispatch a FETCH_NEWS action, requestInProgress is set to true. And every time we dispatch a FETCH_NEWS_SUCCESS or FETCH_NEWS_ERROR action requestInProgress is set to false.
All you have to do now is to check in your component where you are displaying news, if requestInProgress is true and render the loading indicator based on it.
When the component mounts, it will call the fetchNews action and a loading indicator will be displayed in the middle of the screen. If we swipe down when ScrollView is at scrollY: 0
, onRefresh is called, which calls fetchNews, but this time we are refreshing news and the loading indicator is displayed above ScrollView.
That’s all good for now, but where is the problem?
The problem is that in our case news can be FETCHED, PUBLISHED, UPDATED, DELETED, REFRESHED and to display loading indicators for each action on different parts of the screen we would need to have 5 flags, for each action one, in our newsReducer. And there is one more problem, having booleans in reducers for loading flags has its limitations. What if you want to show a loading indicator on a specific news in the list?
To achieve this, we will need to use, instead of boolean, an object that receives the id of the news being deleted.
const initialState = {
...
deleteInProgress: { inProgress:false, newsId:’’}
}
Our newsReducer would end up looking like this.
The size of the reducer has grown, and readability is reduced because of unnecessary code which doesn’t belong to newsReducer. This scenario would likely repeat itself in some other reducer and we would end up repeating code (!DRY).
Let’s separate UI logic from reducers and create uiActions, uiReducer
Our ui action creators will look like:
Our uiReducer will look like:
Our newsSaga will look like:
Our newsReducer will look like:
Look how much newsReducer is cleaner, after we have removed the UI part from it. Now it’s doing what it was supposed to do and that’s managing news related state.
And final but not least our selectors:
Our NewsComponent has to just call the selectors to check which part is currently loading.
What about displaying a loading indicator on a specific news in the list?
We are passing the deletingNewsId, which we got from the getUpdatingItemId selector, to the function which renders the delete button and if the deletingNewsId matches the id of the rendered news, instead of the delete button, a loading indicator is rendered.
In PublishNews component we show the loading indicator if news are published or updated. Again we will use the checkIfLoading selector for that, but this time we are passing multiple actionTypes to it.
Summing up
No approach is perfect so let’s address some of the downsides. Everytime we want to start and stop a loading action we have to do it manually. This introduces boilerplate code in our redux-sagas. At the beginning of the saga, we start the loading action and before the saga finishes we are stoping it, but this is needed to have maximum flexibility, i.e. deciding when the action starts and stops.
Manually starting and stoping loading actions could be prevented if we would refactor our uiReducer like this:
Now every action type that ends with _REQUEST is added to actions object and set to true. The action type is set to false on every _SUCCESS or _ERROR action type, that matches the previous action type name. If you go with this approach you will need to cover edge cases like REFRESHING news (should go into separate object) and DELETING news (the id of the news that is being deleted needs to be stored).
That’s still possible, but your uiReducer would become messy and you wouldn’t be able to decide when exactly will the action start and stop, because it is handled automatically in uiReducer. Maybe you don’t want to start a loading action until some conditions aren’t met, again you can’t control that using this approach. That’s why I prefer the manually way of starting loading actions.
NOTE: don’t put all loading logic into redux blindly, only if the data you are fetching is stored into redux , then the loading logic should be handled in redux-saga, thunk etc. If the data is stored in component state, then the loading flags should also be stored in component state. One example is an autocomplete search component. It should not use redux for storing loading flags, instead it should use component state.
Feel free to leave your comments, questions, suggestions 😃.