How to code split Redux store to further improve your app's performance
Tags: react, redux, javascript, performance
Published: 2018-08-14 09:26:00

---
title: How to code split Redux store to further improve your app's performance
published: true
description: Using redux-store-manager library, we load redux reducers on-demand and improve loading performance of react apps. 
tags: react, redux, javascript, performance
cover_image: https://images.pexels.com/photos/593172/pexels-photo-593172.jpeg
---
These days, to achieve optimal app load times when users visit our website, we are questioning every byte of code that is being transferred on the network.

Let's say a user is visiting the homepage of an e-commerce website (react & redux). To achieve the best [time to interactive](https://developers.google.com/web/tools/lighthouse/audits/time-to-interactive), the javascript bundle should only have the UI components needed to render above-the-fold part of the homepage. We shouldn't load the code of product list or checkout before visiting those pages.

To achieve this you can:
1. lazy load routes - each route's UI components in on-demand bundles.
2. lazy load the components below-the-fold of the page.

**What about reducers?**
Unlike components, the main bundle has all the reducers and not just the ones needed by homepage. The reasons why we couldn't do it were - 
1. The best practice is to keep the redux state tree flat - no parent-child relationships between reducers to create a code-split point.
2. The module dependency trees of components and reducers are not same.
`store.js -imports-> rootReducer.js -imports-> reducer.js(files)` so the store's dependency tree contains all the reducers of the app even if the data stored is used by a main component or an on-demand component.
3. Knowledge of which data is used in a component is business logic or at least isn't statically analyzable - `mapStateToProps` is a runtime function.
4. Redux store API doesn't support code-splitting out of the box and all the reducers need to be part of the rootReducer before creation of the store.
But wait, during development, whenever I update my reducer code my store gets updated via **webpack's Hot Module Replacement**. How does that work? Yes, for that we re-create rootReducer and use **store.replaceReducer API**. It's not as straightforward as switching a single reducer or adding a new one.

*Came across any unfamiliar concepts? Please refer to the links and description below to gain a basic understanding of redux, modules and webpack.*
- Redux - a simple library to manage app state, [core concepts](https://redux.js.org/introduction/coreconcepts), [with react](https://redux.js.org/basics/usagewithreact).
- Modules - [Intro](https://medium.com/@kimberleycook/intro-to-webpack-1d035a47028d), [es6 modules](https://www.sitepoint.com/understanding-es6-modules/), [dynamic import](https://www.sitepoint.com/understanding-es6-modules/)
- Dependency Tree - If `moduleB` is imported in `moduleA`, then `moduleB` is a dependency of `moduleA` and if `moduleC` is imported in `moduleB`, then the resultant dependency tree is - `moduleA -> moduleB -> moduleC`. Bundlers like webpack traverse this dependency tree to bundle the codebase.
- Code-Splitting - When a parent module imports a child module using a dynamic import, webpack bundles the child module and its dependencies in a different build file that will be loaded by the client when the import call is run at runtime.
Webpack traverses the modules in the codebase and generates bundles to be loaded by browser.
![code splitting](https://thepracticaldev.s3.amazonaws.com/i/ukz25x0zjhsu9docg5ob.png)

*Now you are familiar with the above concepts, let's dive in.*

Let's look at the typical structure of a react-redux app - 

```jsx
// rootReducer.js
export default combineReducers({
  home: homeReducer,
  productList: productListReducer
});

// store.js
export default createStore(rootReducer/* , initialState, enhancer */);

// Root.js
import store from './store';
import AppContainer from './AppContainer';

export default function Root() {
  return (
    <Provider store={store}>
      <AppContainer />
    </Provider>
  );
}
```

First you create the rootReducer and redux store, then import the store into Root Component. This results in a dependency tree as shown below
```
RootComponent.js
|_store.js
| |_rootReducer.js
|   |_homeReducer.js
|   |_productListReducer.js
|_AppContainer.js
  |_App.js
  |_HomePageContainer.js
  | |_HomePage.js
  |_ProductListPageContainer.js
    |_ProductListPage.js
```
**Our goal is to merge the dependency trees of store and AppContainer -**
So that when a component is code-split, webpack bundles this component and the corresponding reducer in the on-demand chunk. Let's see how the desired dependency tree may look like - 
```
RootComponent.js
|_AppContainer.js
  |_App.js
  |_HomePageContainer.js
  | |_HomePage.js
  | |_homeReducer.js
  |_ProductListPageContainer.js
    |_ProductListPage.js
    |_productListReducer.js
```
*If you observe. you will notice that there is no store in the dependency tree!*

In the above dependency tree
1. Say `ProductListPageContainer` is dynamically imported in `AppContainer`. Webpack now builds `productListReducer` in the on-demand chunk and not in the main chunk.
2. Each reducer is now imported and registered on the store in a container.

*Interesting! Now containers not only bind data & actions but reducers as well.*

Now let's figure out how to achieve this!

Redux store expects a `rootReducer` as the first argument of `createStore`. With this limitation we need two things -
- Make containers bind reducers before creation of the `rootReducer`
- A higher order entity that can hold the definitions of all the reducers to be present in the `rootReducer` before they are packaged into one.

So let's say we have a higher-order entity called **_storeManager_** which provides the following APIs
- sm.registerReducers()
- sm.createStore()
- sm.refreshStore()

Below is the refactored code & the dependency tree with `storeManager`-

```jsx
// HomePageContainer.js
import storeManager from 'react-store-manager';
import homeReducer from './homeReducer';

storeManager.registerReducers({ home: homeReducer });

export default connect(/* mapStateToProps, mapDispatchToProps */)(HomePage);

// ProductListPageContainer.js
import storeManager from 'react-store-manager';
import productListReducer from './productListReducer';

storeManager.registerReducers({ productList: productListReducer });

export default connect(/* mapStateToProps, mapDispatchToProps */)(ProductListPage);


// AppContainer.js
import storeManager from 'react-store-manager';

const HomeRoute = Loadable({
  loader: import('./HomePageContainer'),
  loading: () => <div>Loading...</div>
});

const ProductListRoute = Loadable({
  loader: import('./ProductListPageContainer'),
  loading: () => <div>Loading...</div>
});

function AppContainer({login}) {
  return (
    <App login={login}>
      <Switch>
        <Route exact path="/" component={HomeRoute} />
        <Route exact path="/products" component={ProductListRoute} />
      </Switch>
    </App>
  );
}

export default connect(/* mapStateToProps, mapDispatchToProps */)(AppContainer);

// Root.js
import storeManager from 'react-store-manager';
import AppContainer from './AppContainer';

export default function Root() {
  return (
    <Provider store={storeManager.createStore(/* initialState, enhancer */)}>
      <AppContainer />
    </Provider>
  );
}
```

Reducers are just registered and Store is created when RootComponent is being mounted. Now this has the desired dependency tree
```
RootComponent.js
|_AppContainer.js
  |_App.js
  |_HomePageContainer.js
  | |_HomePage.js
  | |_homeReducer.js
  |_ProductListPageContainer.js
    |_ProductListPage.js
    |_productListReducer.js
```
Now if `ProductListPageContainer` is on-demand loaded using a dynamic import, `productListReducer` is also moved inside the on-demand chunk.

Hurray! mission accomplished?… Almost

Problem is, when the on-demand chunk is loaded - 
`sm.registerReducers()` calls present in the on-demand chunk register the reducers on the storeManager but don't refresh the redux store with a new `rootReducer` containing newly registered reducers. So to update the store's rootReducer we need to use **redux's store.replaceReducer API**.

So when a parent (`AppContainer.js`) that is dynamically loading a child(`ProductListPageContainer.js`), it simply has to do a `sm.refreshStore()` call. So that store has `productListReducer`, before `ProductListPageContainer` can start accessing the data or trigger actions on, the `productList` datapoint.

```jsx
// AppContainer.js
import {withRefreshedStore} from 'react-store-manager';

const HomeRoute = Loadable({
  loader: withRefreshedStore(import('./HomePageContainer')),
  loading: () => <div>Loading...</div>
});

const ProductListRoute = Loadable({
  loader: withRefreshedStore(import('./ProductListPageContainer')),
  loading: () => <div>Loading...</div>
});

function AppContainer({login}) {
  return (
    <App login={login}>
      <Switch>
        <Route exact path="/" component={HomeRoute} />
        <Route exact path="/products" component={ProductListRoute} />
      </Switch>
    </App>
  );
}
```

We saw how `storeManager` helps achieve our goals. Let's implement it - 

```js
import { createStore, combineReducers } from 'redux';

const reduceReducers = (reducers) => (state, action) =>
  reducers.reduce((result, reducer) => (
    reducer(result, action)
  ), state);

export const storeManager = {
  store: null,
  reducerMap: {},
  registerReducers(reducerMap) {
    Object.entries(reducerMap).forEach(([name, reducer]) => {
      if (!this.reducerMap[name]) this.reducerMap[name] = [];

      this.reducerMap[name].push(reducer);
    });
  },
  createRootReducer() {
    return (
      combineReducers(Object.keys(this.reducerMap).reduce((result, key) => Object.assign(result, {
        [key]: reduceReducers(this.reducerMap[key]),
      }), {}))
    );
  },
  createStore(...args) {
    this.store = createStore(this.createRootReducer(), ...args);

    return this.store;
  },
  refreshStore() {
    this.store.replaceReducer(this.createRootReducer());
  },
};

export const withRefreshedStore = (importPromise) => (
  importPromise
    .then((module) => {
      storeManager.refreshStore();
      return module;
    },
    (error) => {
      throw error;
    })
);

export default storeManager;
```

You can use the above snippet as a module in your codebase or use the npm package listed below - 

{% github sagiavinash/redux-store-manager %}

Say hello to an untapped area of build optimizations :)

Like the concept? - Please share the article and star the git repo :)