Author avatar

Solomon Ayoola

Code-Splitting your Redux Application

Solomon Ayoola

  • Dec 18, 2019
  • 8 Min read
  • 5,972 Views
  • Dec 18, 2019
  • 8 Min read
  • 5,972 Views
Web Development
React

Introduction

Modern web sites often combine all of their JavaScript into a single, large bundle. When JavaScript is served this way, loading performance suffers. Large amounts of JavaScript can also tie up the main thread, delaying interactivity. This is especially true of devices with less memory and processing power.

An alternative to large bundles is code-splitting, which is where JavaScript is split into smaller chunks. This enables sending the minimal code required to provide value up front, improving page-load times. The rest can be loaded on demand.

In this guide, we will be talking about code-splitting in the context of React, Redux, and React-Redux applications.

This guide assumes some level of familiarity with React, Redux, and React-Redux.

What is Code-Splitting?

Code-splitting is the act of deferring the import of some portion of our JavaScript until it is needed at a later point in time by a user interaction such as button click, scrolling, typing, etc.

Code-splitting helps reduce the amount of JavaScript that is needed to make our app load as quickly as possible, thereby maximizing user engagement and improve page load times.

Code-splitting is a feature supported by bundlers like Webpack, Rollup and Browserify (via factor-bundle), which can create multiple bundles that can be dynamically loaded at runtime.

Code-Splitting in a React Component

The preferred way to introduce code-splitting in a React component is via dynamic import(). The import() function-like form takes the module name as an argument and returns a Promise, which always resolves to the namespace object of the module.

1import('./Module').then(Module => Module.method())
javascript
1// SomeComponent.js
2const SomeComponent = () => <p>This is a test component</p>
3
4export default SomeComponent
jsx
1// App.js
2import React, { Component } from 'react'
3
4class App extends Component {
5  handleClick = () => {
6    import('./SomeComponent')
7      .then(({ SomeComponent }) => {
8        // Use SomeComponent
9      })
10      .catch(err => {
11        // Handle failure
12      });
13  };
14
15  render() {
16    return (
17      <div>
18        <button onClick={this.handleClick}>Click Me</button>
19      </div>
20    );
21  }
22}
23
24export default App;
jsx

The example above includes SomeComponent.js as a separate chunk that only loads after the Click Me button has been clicked.

React.lazy method makes it easy to code-split a React application on a component level using dynamic imports.

React.lazy takes a function that must call a dynamic import(). This must return a Promise which resolves to a module with a default export containing a React component.

1const SomeComponent = React.lazy(() => import('./SomeComponent'));
javascript

The lazy component should then be rendered inside a Suspense component, which allows us to show some fallback content (such as a loading indicator) while we’re waiting for the lazy component to load.

1const SomeComponent = React.lazy(() => import('./SomeComponent'));
2
3function MyComponent() {
4  return (
5    <div>
6      <Suspense fallback={<div>Loading...</div>}>
7        <SomeComponent />
8      </Suspense>
9    </div>
10  );
11}
jsx

Suspense fallback props accepts any React element which is being rendered while waiting for the component to load. We can place the Suspense component anywhere above the lazy component. We can even wrap multiple lazy components with a single Suspense component.

1const SomeComponent = React.lazy(() => import('./SomeComponent'));
2const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
3
4function MyComponent() {
5  return (
6    <div>
7      <Suspense fallback={<div>Loading...</div>}>
8        <SomeComponent />
9        <AnotherComponent />
10      </Suspense>
11    </div>
12  );
13}
jsx

React.lazy currently only supports default exports.

Route-Based Code-Splitting

Here’s an example of how to setup route-based code-splitting into our app using libraries like React Router with React.lazy.

1// App.js
2import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
3import React, { Suspense, lazy } from 'react';
4
5const Home = lazy(() => import('./routes/Home'));
6const About = lazy(() => import('./routes/About'));
7
8const App = () => (
9  <Router>
10    <Suspense fallback={<div>Loading...</div>}>
11      <Switch>
12        <Route exact path="/" component={Home} />
13        <Route path="/about" component={About} />
14      </Switch>
15    </Suspense>
16  </Router>
17);
jsx

Code-Splitting with Redux

From the previous sections, we have been able to demonstrate how we can load our React components dynamically, but we still need to get the right data into our modules as they load.

Redux as a state management library allows us to provide reducer functions at the time we create the store with createStore function but does not give us the ability to to register reducer functions on demand. So how do we achieve that?

It turns out Redux store API exposes a replaceReducer function that replaces the current active root reducer function with a new root reducer function.

1// store.js
2import { combineReducers, createStore } from "redux"
3
4const initialState = {}
5const store = createStore(createReducer(), initialState)
6
7const newRootReducer = combineReducers({
8  existingSlice: existingSliceReducer,
9  newSlice: newSliceReducer
10})
11
12store.replaceReducer(newRootReducer)
javascript

We could go one step further by creating a reusable injectReducer function in addition to the replaceReducer that keeps references to all of the existing slice reducers, and attach that to the store instance.

1// reducers.js
2import { combineReducers } from 'redux'
3
4const createReducer = (asyncReducers) => {
5  return combineReducers({
6    ...asyncReducers
7  })
8}
9
10export default createReducer
javascript
1// store.js
2import { createStore } from "redux"
3import createReducer from "./reducers"
4
5const store = createStore(createReducer())
6
7export default function configureStore() {
8  // Add a dictionary to keep track of the registered async reducers
9  store.asyncReducers = {}
10
11  // Create an inject reducer function
12  // This function adds the async reducer, and creates a new combined reducer
13  store.injectReducer = (key, asyncReducer) => {
14    store.asyncReducers[key] = asyncReducer
15    store.replaceReducer(createReducer(store.asyncReducers))
16  }
17
18  // Return the modified store
19  return store
20}
21
22export function getStore() {
23  return store
24}
javascript

Note: injectReducer is not part of the redux store API.

The usage looks like this:

1// App.js
2import React from "react";
3import { getStore } from '../store'
4
5const store = getStore()
6
7const Section = React.lazy(() => import('../containers/Section')
8  .then(async module => {
9  const todos = await import('../reducers/todos')
10    .then(todosModule => todosModule.default)
11
12  store.injectReducer('todos', todos)
13  return module
14}))
15
16const App = () => (
17  <React.Suspense fallback={<div>loading...</div>}>
18    <MainSection />
19  </React.Suspense>
20);
21
22export default App;
jsx

A few Redux libraries that are worth checking out:

The link below points to a working example of a todo application that uses React.lazy, React.Suspense and Redux.

Edit

Conclusion

In this guide, we have learned how code-splitting can help us improve loading time and page performance by loading Javascript on demand until it is needed.

Here are a few resources on Code-Splitting to check out: