The classic way
When you learn how to perform async operations using Redux this is how it usually works.
The user interacts with the UI triggering a fake action that doesn’t update the store, it serves as an initiator for some kind of async operation against a server, and if the operation is successful we update the store to reflect such success, on the contrary, if the operation fails, we show an error and if needed we update the store to reflect the error.
In the meanwhile, the user is staring at some spinner or loading indicator and the UI won’t reflect any other change until the whole operation finishes or fails, and that could potentially take a long time when connected over a 2G or 3G network, or when the server is busy doing another task.
We as Users know that looking at a loading indicator is not fun nor an engaging experience when using a mobile app or a website.
The classic async flow with Redux
This is how we would handle an async operation in the typical counter reducer example.
As you can see, the user needs to wait for the async operation to finish before he notices a change in the UI. We could say that this approach is a pessimistic one because the UI doesn’t assume that the operation will succeed and will await until being sure that the operation finished successfully or failed before changing the UI.
The concept of optimistic UI comes from the UI showing immediate response to a user’s actions as if it knew that the operation will succeed, and, in case of failure, it will revert its state and inform the user that the operation failed.
We’ve been using apps implementing these features without knowing for years, one of the most popular cases is Facebook Like button counter. When you press a Like button in the Facebook App, the counter goes up immediately even in a 2G network, that makes you feel that the app is super fast and gives the user a great experience, unlike staring at a spinner or loading indicator until the like reaches Facebook’s servers.
One of the required features to implement an Optimistic UI is being able to handle failure on the underlying async operation. The idea is to revert an action dispatched and get the UI to a valid state like if the operation never existed, and at the same time notify the user about the failure (and perhaps give the option to retry).
This is how an optimistic counter could be implemented using Thunk or Saga
As seen, if the call to the server fails, we dispatch the decrement action to bring back the state to how it was before the optimistic update. This approach is fine for simple cases, but creating an opposite action for every action in our reducer is not very efficient programming-wise. The ideal scenario would be being able to undo a specific action without relying on another action with the explicit opposite effect.
To be able to undo a specific action instead of having to dispatch an action with the opposite effect, we will use the redux-undo-action package.
npm i redux-undo-action
Now we need to wrap our top-level reducer with the undoable reducer enhancer.
Now, we can undo any action dispatched to our store.
Now we don’t need to dispatch the decrement action anymore since our store knows how to undo an increment.
The Optimisitc UI technique provides an improved User Experience that is very noticeable in high latency or low-speed networks and it should be considered for every app, especially in mobile-first websites or mobile apps.
With Redux this UI pattern is very easy and straightforward to implement and will go a long way on improving the engagement of your Users, and the overall feeling of fast responsiveness that we all love feeling when using an App.