Nov 25, 2019 | 5 min read

Code Splitting with React and Redux

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 upfront, 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 a React, Redux, React-Redux Application

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 in reducing the amount of JavaScript that is needed to make our app load as quickly as possible thereby maximising 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 most 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 elements 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 Component's dynamically,but yet 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 to we achieve that?

It turns 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; }

NB: injectReducer is not part of the redux store API

And 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;

Some few redux libraries that is worth checking out are

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

Edit todomvc


We have been able to see how Code Splitting can be help us improve loading time, improve page performance by loading Javascript on demand until it is needed.

Here are some few resources on Code-Splitting to check out