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.
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
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.
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
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
combineReducerscall, 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_POSTSaction. 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 use
combineReducersat 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
createReducerfunction 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.
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
Here’s the reducer code we originally wrote for this:
combineReducers, we ended up with:
Notice how much less code there is in both examples because of the use of
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.
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.