Skip to content

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

Code-Splitting your Redux Application

Code splitting enables sending minimal code up front to provide value, while the rest of the code can be loaded on demand.

Dec 18, 2019 • 8 Minute Read

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.

      import('./Module').then(Module => Module.method())
    
      // SomeComponent.js
const SomeComponent = () => <p>This is a test component</p>

export default SomeComponent
    
      // App.js
import React, { Component } from 'react'

class App extends Component {
  handleClick = () => {
    import('./SomeComponent')
      .then(({ SomeComponent }) => {
        // Use SomeComponent
      })
      .catch(err => {
        // Handle failure
      });
  };

  render() {
    return (
      <div>
        <button onClick={this.handleClick}>Click Me</button>
      </div>
    );
  }
}

export default App;
    

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.

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

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.

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

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <SomeComponent />
      </Suspense>
    </div>
  );
}
    

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.

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

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <SomeComponent />
        <AnotherComponent />
      </Suspense>
    </div>
  );
}
    

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.

      // App.js
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import React, { Suspense, lazy } from 'react';

const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));

const App = () => (
  <Router>
    <Suspense fallback={<div>Loading...</div>}>
      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/about" component={About} />
      </Switch>
    </Suspense>
  </Router>
);
    

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.

      // store.js
import { combineReducers, createStore } from "redux"

const initialState = {}
const store = createStore(createReducer(), initialState)

const newRootReducer = combineReducers({
  existingSlice: existingSliceReducer,
  newSlice: newSliceReducer
})

store.replaceReducer(newRootReducer)
    

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.

      // reducers.js
import { combineReducers } from 'redux'

const createReducer = (asyncReducers) => {
  return combineReducers({
    ...asyncReducers
  })
}

export default createReducer
    
      // store.js
import { createStore } from "redux"
import createReducer from "./reducers"

const store = createStore(createReducer())

export default function configureStore() {
  // Add a dictionary to keep track of the registered async reducers
  store.asyncReducers = {}

  // Create an inject reducer function
  // This function adds the async reducer, and creates a new combined reducer
  store.injectReducer = (key, asyncReducer) => {
    store.asyncReducers[key] = asyncReducer
    store.replaceReducer(createReducer(store.asyncReducers))
  }

  // Return the modified store
  return store
}

export function getStore() {
  return store
}
    

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

The usage looks like this:

      // App.js
import React from "react";
import { getStore } from '../store'

const store = getStore()

const Section = React.lazy(() => import('../containers/Section')
  .then(async module => {
  const todos = await import('../reducers/todos')
    .then(todosModule => todosModule.default)

  store.injectReducer('todos', todos)
  return module
}))

const App = () => (
  <React.Suspense fallback={<div>loading...</div>}>
    <MainSection />
  </React.Suspense>
);

export default App;
    

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.

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: