High Order Reducers — It just sounds scary
How to separate your reducer’s logic into small composable and testable building blocks
In a nutshell
A reducer is a pure function for handling state changes in a Redux-base application.
If you’re not familiar with redux and reducers, please refer to this guide first-
High Order Reducers — The lego bricks
High Order Reducer (HOR from now on), is a design pattern created for reusing reducer’s logic. An HOR enhance your reducer, make it “listen” to more actions and manage a bigger state.
For example, we can create an “Errorable” HOR that listens to failure actions and update the state’s “errorMessage”. (We’ll return to the errorable HOR will shortly).
Like lego bricks we can build an entire reducer just from smaller HORs
In this article I want to share with you a HORs implementation we use in our application, so you could consider use it in your own application.
I wanted to create a framework that enables the developer to write a reducer’s logic based on his/her business logic only, and not care about the global HORs that will enhance his code.
- Single responsibility — Each HOR responsible for a certain logic.
- Initial Values for HOR state — when adding an HOR to your reducer, you don’t need to declare the initial state of the HOR’s state
- Simplicity — It’s very clear what your reducer is response for
- Easy to test: — Because the HOR is decoupled from your reducer, you can easily test the HORs and your reducer separately.
Small building blocks
We will create a bunch of small HORs, each responsible for a targeted ability, for example:
errorable — handles “FAILURE/SUCCESS/PENDING” actions and updates the state. For example when dealing with async actions.
resetable — add the “reset” option for a reducer — return to the initial state when an “RESET” action fires.
matserDetails — can handle open/close details actions and selecting a specific item
Safes reducer example
Let’s create a reducer for an imaginary application that manages Banks.
This is a large application that has many features (hence — many reducers)
In one of the application’s pages we want to show to user all his/her safes.
We’ll create a “BaseSafesReducer” that handles bunch of safes-related actions-
“GET_SAFES”, “GET_SETTINGS”, “SET_SEARCH” etc.
The state this reducer is handles is also only “safes-related”.
Look at the diagram bellow.
The first thing we do is to create our base reducer.
Now, since some of the base actions are async, we also want to handle errors from the server, don’t we? lets enhance the base reducer with an Errorable HOR.
Finally, we can keep enhancing our base reducer and add more capabilities for it, as you can see in the 3rd diagram.
Our base reducer-
This function is the secret behind all this magic.
Under the hood, what it does is this:
// You don't want to write this kind of code do you?
safeReducer = errorable(resetable(tabable(masterDetails(baseSafesReducer))))
And this is the way for you to implement it, as a basic util in your application:
You have decide to reuse a logic you have and create a new HOR? great!
Each HOR, receives parameters to work with, and the reducer it self.
It’s important to send the reducer after we send all the other parameters.
This is called curry, and that’s necessary for the extendReducer function to work.
Example of a super simple HOR — Tabable.
The Tabable HOR listens to “SET_SELECTED_TAB” actions and update the state accordingly.
Since we’ve decoupled the common logic from our business logic, now we can easily write unit tests for our HORs and “business” reducers.
The reducers and HORs are all pure functions by design, what makes them even less of a challenge when it comes to unit testing.
I recommend using the test-reducer framework for testing your reducers.
HORs are great way to create a reusable logic for your state management reducers.
It makes your code easy to read and maintain, increase your development rate and allows you to easily test your reducers.