Yes, Higher-Order Reducers Are A Thing!

This blog is intended for developers who are familiar with building React apps with Redux and wants to learn about implementing higher-order combine reducers to manage Redux state tree.

How many of you have already used Redux’s combine reducers in your projects? I assume all of you reading this blog or maybe 99.9% of you. To recap, a combine reducer is a built-in function provided by Redux that accepts an object of sub-reducers and returns a new state object derived from the previous state and output of each of the sub-reducers. It takes a shape as follows:


import { combineReducers } from 'redux';
import bookReducer from './book';
import reviewReducer from './review';
const rootReducer = combineReducers({
book: bookReducer,
review: reviewReducer
});
export default rootReducer;

view raw

index.js

hosted with ❤ by GitHub

The main responsibility of combine reducers is to allocate responsibility for each of the sub-reducers to maintain its respective parts of the application’s state. This pattern help developers to create a small, testable and easily manageable reducers.

CRUX

Coming to the crux of this blog – what are higher-order combine reducers? In JavaScript, higher-order functions are functions that take functions as arguments and/or return functions as output. Going by this definition, higher-orders are combine reducers that take other combine reducers. It would be helpful to understand this with an example:

Imagine we are building a movie ticket application that allows users to purchase tickets. The application would list all the tickets in its homepage(or the tickets page) and allow users to checkout them in the checkout page. Simple?

Applying route-based state management produces the following directory structure:

|__src
   |__ticketPage
      |__reducers
         |__ticketReducer.js
   |__checkoutPage
      |__reducers
         |__paymentReducer.js
         |__accountInfoReducer.js
         |__checkoutReducer.js
    |__common
      |__reducers
         |__index.js

and following state tree:


{
ticket: { tickets: [] },
checkout: {
payment: {},
accountInfo: {}
}
}

view raw

model.js

hosted with ❤ by GitHub

As you take an educated guess, each part of the state is independently managed by its respective reducer, i.e., ticketis maintained by ticketReducerand it is not aware of other parts of the state. Since the checkoutsubtree has its own subtree paymentand accountInfo, it is an ideal candidate for using combine reducers to handle them. It is written as follows:


import { combineReducers } from 'redux';
import accountInfoReducer from './accountInfoReducer';
import paymentReducer from './paymentReducer';
const checkoutReducer = combineReducers({
accountInfo: accountInfoReducer,
payment: paymentReducer
});
export default checkoutReducer;

Our effort to implement higher-order combine reducers is not far away! Remember, higher-orders are combine reducers that take other combine reducers so we just need to write one more combine reducers and pass in the above combine reducers to connect the pieces of state together. Where else can we write one? We can write it in a root or global and this must be the server of the global state formed by merging all the sub-trees together. This is as follows:


import {combineReducers} from 'redux';
import ticketReducer from '../../ticketPage/reducers/ticketReducer';
import checkoutReducer from '../../checkoutPage/reducers';
const rootReducer = combineReducers({
ticket: ticketReducer,
checkout: checkoutReducer
});
export default rootReducer;

view raw

index.js

hosted with ❤ by GitHub

In the above example, rootReducerallocates responsibility to manage the ticketpart of the state to ticketReducerand checkoutpart of the state to checkoutReducerwhich is nothing but another combine reducers – breaking the responsibility in to pieces and assigning in to many. This pattern can be compared with the concept of helper functions in programming in general where we divide a large problem in to sub-problems and use multiple helpers to solve them.

Conclusion

We have seen the implementation of higher-order combine reducers. This pattern completely breaks down the responsibility of sub-reducers even further as opposed to have just one combine reducers. I hope this is an useful pattern that comes handy in your projects.

Resource: Github Repo

Leave a comment