Taking Advantage of combineReducers
If you’ve been using Redux for long, you’ve probably used its combineReducers
function to create your application’s root reducer. But you may not have thought of using it in your sub-reducers as well. Let’s see how we can take advantage of combineReducers
to simplify our code.
Introduction
If you’ve worked on a Redux app of any size, you’re probably familiar with combineReducers
. The most common use case is to create your root reducer with it.
For example, the Async Example in the Redux documentation contains the following rootReducer
definition:
This usage of combineReducers
is pretty common and well-documented.
But have you thought about using combineReducers
for the lower levels of your state tree as well? Let’s look at some examples.
Async Example
Here’s the entire reducer file from the same Async Example:
Let’s look at the posts
reducer a little more closely. Notice that its initial state is an object with three keys and that the handler for RECEIVE_POSTS
adds in a fourth.
What if we split posts
into pieces that we re-assemble with combineReducers
?
Notice how the individual reducers and their initial states are much simpler? I find that it’s much easier to write my reducers this way, and its also much easier to understand them. I can think about each section of the state tree independently of the others, and I don’t have to do any complicated merging or object construction.
I should point out one slight change. In the original code, the initial value of the lastUpdated
key is undefined
because it doesn’t appear in the initial state. When I split out a separate reducer for that key, I had to change the initial value to null
. This is because Redux warns you if a reducer produces undefined
as its result. Normally when that happens, it’s an error, so it warns you about it.
There are a few disadvantages to this decomposition approach, though, and you’ll have to weigh those against the simpler code.
-
It’s a bit harder to see the overall shape of the state tree at a glance. You can see all of the keys in the state by looking at the
combineReducers
call, but to see what the value of each key looks like, you have to look at the individual reducers. I haven’t found this to be a big deal. In fact, writing my reducers this way means that I don’t really need to think about the overall shape of the state tree as much as I used to. -
I have to look in more places to see all of the effects of a single action. Notice that all four of the individual reducers handle the
RECEIVE_POSTS
action. If I keep all of the individual reducers in the same file, it’s not too bad, but if I split them into different files, it gets harder. However, this is also the case if you usecombineReducers
at a higher level. Again, I haven’t found this to be a big problem, because I tend to think about each sub-section of the state tree independently now. -
There are more lines of code now. This will be true almost any time you extract functions out of a bigger function. In my opinion, the simpler code makes this a good tradeoff. I always use a
createReducer
function as described in the Reducing Boilerplate section of the Redux documentation to make this overhead smaller. The next example will show what this looks like.
Error Messages
My current project is a React Native application. The application has some background API calls going on. If one of those fails, we want to report the error in an animated slide-out panel.
We represent this in the state tree with a simple object containing a hasError
key and an errorMessage
key. Normally, we could just have the errorMessage
and show or hide the error panel accordingly, but because the panel slides in and out, we need the error message to stay in place until the animation completes. Thus, we control the panel with the hasError
flag and keep the errorMessage
around.
Here’s the reducer code we originally wrote for this:
After applying combineReducers
, we ended up with:
Notice how much less code there is in both examples because of the use of createReducer
.
But the refactored example is even shorter than the original because the handler functions became so simple that I was able to inline them right into the createReducer
calls. Before refactoring, they were long enough that I felt it better to extract them to their own named functions.
Conclusion
I encourage you to take a look at your reducers and see if you can simplify them by applying combineReducers
at lower levels of your state tree.
I typically look for cases where the initial state is an object; these are good candidates for this refactoring. However, if the object is really a domain entity (like a user or a todo, for example), I typically won’t decompose the reducer like this. But in all other cases, where the object is a collection of semi-independent keys, I’ll think seriously about applying this refactoring.
The Redux documentation has an entire section on Structuring Reducers that talks about this technique and many others. It’s well worth taking the time to read through it.