Combining Reducers

Posted by kriti-rai on March 13, 2019

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.

app gif

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!

office space

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.

reducer

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

restaurantReducer

reviewReducer

reviewReducer

Now, here’s our rootReducer, where we will call our children reducer functions. Notice, we imported combineReducers from redux.

rootReducer

rootReducers

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.