The Ultimate Cheat Sheet on Splitting Dynamic Redux Reducers

This post is specific to need of code-splitting in React/Redux projects. While exploring the possibility to optimise the application, the common problem occurs with reducers. This article specifically focuses on how do we split reducers to be able to deliver them in chunks.

What are the benefits of splitting reducers in chunks?

1) True code splitting is possible

2) A good architecture can be maintained by keeping page/component level reducers isolated from other parts of application minimising the dependency on other parts of application.

Why Do We Need to Split Reducers?

1. For fast page loads

Splitting reducers will have and advantage of loading only required part of web application which in turn makes it very efficient in rendering time of main pages


2. Organization of code

Splitting reducers on page level or component level will give a better code organization instead of just putting all reducers at one place. Since reducer is loaded only when page/component is loaded will ensure that there are standalone pages which are not dependent on other parts of application. That ensures seamless development since it will essentially avoid cross references in reducers and throwing away complexities

3. One page/component

One reducer design pattern. Things are better written, read and understood when they are modular. With dynamic reducers it becomes possible to achieve it.

4. SEO

SEO is vast topic but it gets hit very hard if your website is having huge response times which happens in case if code is not split. With reducer level code splitting, reducers can be code split on component level which will reduce the loading time of website thereby increasing SEO rankings.

What Exists Today?

A little googling around the topic shows us some options. Various ways has been discussed at https://stackoverflow.com/questions/32968016/how-to-dynamically-load-reducers-for-code-splitting-in-a-redux-application/33044701#33044701 .

Dan Abramov’s answer is what we are following in this post and we will be writing a simple abstraction to have dynamic reducers but with more functionality.

A lot of solutions already exists, so why do we need to create our own? The answer is simple and straightforward:

    1) The ease of use

Every library out there is little catchy is some way. Some have complex api’s while some have too much boilerplate codes. We will be targeting to be near react-redux api.

    2) Limitation to add reducers at top level only

This is a very common problem that a lot of existing libraries have as of today. That’s what we will be targeting to solve in this post. This opens new doors for possibilities to do code splitting on component level.

A quick recap of redux facts:

1) Redux gives us following methods:
- “getState”,
- “dispatch(action)”
- “subscribe(listener)”
- “replaceReducer(nextReducer)”

2) reducers are plain functions returning next state of application

3) “replaceReducer” requires the entire root reducer.

What we are going to do?

We will be writing abstraction around “replaceReducer” to develop an API to allow us to inject a reducer at a given key dynamically.

A simple Redux store definition goes like the following:

Let’s simplify the store creation wrapper as:

What it Does?

“dynamicActionGenerator” and “isValidReducer” are helper function to determine if given reducer is valid or not.

For e.g.

isValidReducer(() => { return  ) // should return true
isValidReducer(1) //should return false
isValidReducer(true) //should return false
isValidReducer(“example”) //should return false

This is an essential check to ensure all inputs to our abstraction layer over createStore should be valid reducers.

“createStore” takes initial Root reducer, initial state and enhancers that will be applicable to created store.

In addition to that we are maintaining, “asyncReducers” and “attachReducer” on store object.

“asyncReducers” keeps the mapping of dynamically added reducers.

“attachReducer” is partial in above implementation and we will see the complete implementation below. The basic use of “attachReducer” is to add reducer from any part of web application.

Given that our store object now becomes like follows:

Store:

	- getState: Func
	- dispatch(action): Func
	- subscribe(listener): Func
	- replaceReducer(RootReducer): Func
	- attachReducer(reducer): Func
	- asyncReducers: JSONObject 

Now here is an interesting problem, replaceReducer requires a final root reducer function. That means we will have to recreate the root reducers every time.
So we will create a dynamicRootReducer function itself to simply the process.

So now our store object becomes as follows:
Store:

	- getState: Func
	- dispatch(action) : Func
	- subscribe(listener) : Func
	- replaceReducer(RootReducer) : Func
	- attachReducer(reducer) : Func

What does dynamicRootReducer does?
1) Processes initial root reducer passed to it
2) Executes dynamic reducers to get next state.

So we now have an api exposed as :
store.attachReducer(“home”, (state = {}, action) => { return state }); // Will add a dynamic reducer after the store has been created

store.attachReducer(“home.grid”, (state={}, action) => { return state}); // Will add a dynamic reducer at a given nested key in store.

Final Implementation:

Working Example: https://stackblitz.com/edit/react-pvk4xw?file=Hello.js


Further implementations based on simplified code:

Based on it I have simplified implementations into two libraries:

Conclusion

In this way, we can achieve code splitting with reducers which is a very common problem in almost every react-redux application. With above solution you can do code splitting on page level, component level and can also create reusable stateful components which uses redux state. The simplified approach will reduce your application boilerplate. Moreover common complex components like grid or even the whole pages like login can be exported and imported from one project to another making development faster than ever!

About the Author

17445338.jpg

Saurabh is Technical Lead at Velotio. He is Full stack engineer with strong focus on security principles.  He is having keen interest in solving puzzles, Rubik cubes and building unique software ideas.