Sharing Independently Managed Redux State Subtrees Across Reducers

This blog is intended for developers who are familiar with building React apps with Redux. In this blog, I would like to share one of the challenges I faced when working with Redux combine reducers and how I overcame it.

Problem

When working on my recent project, I employed “reducer separation” strategy enabled by combine reducers to allocate responsibility for each of the reducers to manage independent subtree of the application’s state. This has resulted in a small, testable and easily manageable reducers. Each of the reducers received only subtrees of the whole state tree, without even being aware of the other parts of the state. Although this is an ideal functionality, sometimes we want to derive new parts of the state based on other parts of the state, in other words, we want to access a slice of state in other reducers and this is not possible since combine reducers adheres to the single responsibility principle. In this blog, we will see how I overcome this restriction by writing my own combine reducer.

SEEING PROBLEM WITH EXAMPLE

The problem mentioned above is demonstrated with the following example:


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


const book = (() => {
return {
books: []
};
})();
const bookReducer = (state=book, action) => {
console.log(state); //=> { books: [] }
//logic here
};
export default bookReducer;

view raw

bookReducer.js

hosted with ❤ by GitHub


const review = (() => {
return {
reviews: []
};
})();
const reviewReducer = (state=review, action) => {
console.log(state); //=> { reviews: [] }
//logic here
};
export default reviewReducer;

What is happening in this example?

We have two parts of the application state, reviewand bookand they are managed by reviewReducerand bookReducer, respectively. When actions are dispatched, Redux will invoke each of the reducers and provide its respective parts of state(as inspected from the logs).

As you can see, accessing both reviewstate and bookstate in any of these reducers is not possible since we are using Redux’s combine reducers. But what if need access to reviewdata from the bookReducerfunction? Since combine reducers are just functions, we can create our own custom combine reducer to pass the necessary data to any reducers.

Custom Combine Reducer


import bookReducer from './book';
import reviewReducer from './review';
const rootReducer = (state={}, action) => {
return {
review: reviewReducer(state.review, action),
book: bookReducer(state.book, {…action, …state.review})
};
};
export default rootReducer;

view raw

index.js

hosted with ❤ by GitHub

The implementation of our custom combine reducer is very similar to the Redux’s combine reducer. All it does is create a new state object by combining the previous state and the output of each of the sub-reducers. In order to provide reviewdata to bookReducer,we are intercepting the action and adding thereviewdata before calling the bookReducer,thus providing it with necessary data.

Custom combine reducer pattern works well but if you find yourself in a situation pass data to across many reducers frequently, then it is advisable to merge related data and handle them together.

Code to the above example can be found in my Github repository: https://github.com/ahamedali95/redux-custom-combiner-reducers

 

 

Leave a comment