I was recently working on a lab in building a Yelp-like application that uses React and Redux to add and delete restaurants and their reviews.
While working my way through the lab I found that my reducer function, manageRestaurants
, was dense. So, I naturally sought to split my giant reducer function into two children reducer functions so that each function was responsible for only one resource’s state. Using combineReducers, I then combined the children reducers in one parent reducer function, rootReducer
, which is what gets passed to the store. This not only made my code cleaner but also much easier to debug.
Finally, I got the app working in the browser just as the lab wanted and before I could take that big sigh of relief I found that the tests were failing. The lab just wanted us to create one reducer function and put all reducer logic in there. Ugh!
Regardless, I decided to create a separate branch and push up my clean and amazing code there and reverted my master
branch to the old way to pass the tests. In so doing, however, I realized that now I had a greater understanding of how combineReducers
works. Additionally, now that I had seen both scenarios I could use that knowledge and experience to decide when I could use combineReducers
. If you are just working with one or two resources, maybe you don’t quite need to use this helper function. However, imagine a big app with multiple resources and soon enough you will find yourself tangled up in a number of switch statements and a big, fat state with multiple key-value pairs.
Refactoring with combineReducers
All talks aside, lets look at my giant reducer manageRestaurants
first, which is maintaining the state of both restaurants and reviews.
Now, let’s split our giant reducer into two child reducer functions, say restaurantReducer
and reviewReducer
. The former manages the state of the restaurants whereas the latter manages the state of the reviews.
restaurantReducer
reviewReducer
Now, here’s our rootReducer
, where we will call our children reducer functions. Notice, we imported combineReducers
from redux
.
rootReducer
This is equivalent to writing:
function rootReducer(state = {}, action) {
return {
restaurants: restaurantReducer(state.restaurants, action),
reviews: reviewReducer(state.reviews, action),
};
};
This basically produces the same giant reducer function as manageRestaurants
does but in a much more abstract and cleaner way.
Conclusion
If your app is big and has more than one or two resources, you might be better off splitting up the state object into slices and using a separate child reducer to operate on each slice of the state. The slices of state can then be combined using combineReducers
, a helper utility lent by Redux, in a parent reducer, conventionally named rootReducer
. Keep in mind that using combineReducer
might not be helpful if one is intending to learn what is going under the hood as it abstracts the way reducers are being combined and working together. So, try to play around with both scenarios to get a better understanding of how reducers work and when to use combineReducers
.