Tools to ease Redux actions and reducers development
I have been working on many React and React Native projects this past year, and all of them use Redux. Thanks to the various patterns present in those projects, at Wolox we developed redux-recompose in order to abstract and improve these patterns.
When I watched the lessons of Dan Abramov’s Egghead course to learn Redux for Wolox trainings, I felt that using a switch in a reducer was smelly.
We started writing reducers in a traditional way, with that switch. In the first project we developed in React Native, this pattern became unhandy:
Since reducers grew too much, we started to disable
Another issue we have encountered is that async actions, nearly in a 90% of cases dispatched a SUCCESS or a FAILURE action. Although this is not an issue per se, it introduced too much duplicated code:
Reducer handlers as Objects
One day, one of my coworkers said:
‘We can try using an object instead of a switch for reducers. Switch cases can be extracted in smaller, testeable functions.’
From a Dan Abramov comment:
Reducer is just a function. How you structure it and whether you split it into many and call other functions is completely up to you.
That comment encouraged us to explore other ways to write reducers. Which lead us to this:
Handlers for SUCCESS and FAILURE actions still looked too similar; only the ‘target’ of these actions were different. We denote target of an action from that part of the state being modified. For example, if we update a list from
state.aList, ‘aList’ is the target of the reducer.
What if we can define from the action-side what part of the state will be affected by the action?
Think of action as a “message”. The action doesn’t know how the state changes. It’s precisely reducers’ job.
What if actions know what part of the state change? That’s the concept of targeted actions. Targeted actions look like this:
Introducing the concept of effect
If you’ve ever used redux saga you’ve probably thought about those effects. Well, these are a little bit different.
The idea here is to decouple reducers from the operations that they do over the state. These operations can be extracted as effects — functions that always do the same over the state, but they don’t know what part of the state changes:
Notice that we can handle a function with these effects. These functions are called selectors. Selectors may lend a hand for relevant data wrapped in an object:
With that in mind, these handlers now look like this:
This idea isn’t new:
But, there is code that is still being repeated. For every primary action (i.e. actions that have associated SUCCESS and FAILURE actions), we must write a SUCCESS and a FAILURE effect. Is there a way to extract code patterns like this one?
Completers to the rescue
Completers are meant to extract patterns that cause repeated logic. For example, we could extract SUCCESS-FAILURE from the reducer.
We can reduce this code:
completeReducer is a function that takes a reducer description and extends SUCCESS and FAILURE cases for all primary actions. Also, it supports a field named
override to provide actions that aren’t primary.
Since writing state fields from SUCCESS and FAILURE cases also might be a bit annoying,
error for us:
And for action names, adding
These completers take another param for the exceptions cases — those items that we don’t want to extend.
Since SUCCESS-FAILURE is a very common pattern, completers are oriented to complete this by default. Currently, we are working on custom completers that take custom rules for completion, they will be available soon.
Craft your own async action with Injections
What about async actions? Do they work here?
Yes! They do. In most cases, we write async actions to fetch things from a service and put them in the store’s state.
We can write async actions as:
Those are conceptually the same as the first ones: they put loading flag in true, and according to the service response, dispatch a SUCCESS or a FAILURE action. This way, we have extracted the many repeated logic; also eliminated the need of declaring a
But, what if we want to customize this behavior, by adding code between calls or dispatches?
We could achieve that with injections — functions that add behavior to baseThunkAction.
These examples are both equal conceptually:
More detailed documentation is available at https://github.com/Wolox/redux-recompose
npm package is available:
npm install --save redux-recompose
Also I’d like to thanks everyone at Wolox who helped build this project.
If you have any suggestions, any ideas you want to talk about, or find a bug, please post an issue or create a PR on GitHub and I’ll gladly reply it. Let us know what you think !
Stay tuned !