Code Splitting AKA Lazy Loading in React

Uriel Rodriguez
The Startup
Published in
6 min readSep 13, 2020

Code organization is important for the maintainability, scalability, and efficiency of a program. For example, a web application can be organized using links which lead to individual pages that separate how information and functionality is served to the user. In React, code is organized into their own components, and those components can be rendered specifically and intentionally on particular events, such as visiting a specific route or url path. An example is visiting the “home page/aboutme” url, which would trigger the rendering of a component that is responsible for loading the about me page.

As a program grows in complexity, more of this separation of code can be used to maintain organization. However, as a program becomes larger it also creates problems with efficiency. This is because, by default, an application is served in its entirety via the bundle.js file which stores all the code of the application in a condensed and optimized form. A large enough application can become inefficient when loading, especially if there are parts that are not utilized by the user (a user does not visit a certain page or use a certain feature of an application). In this context, loading those parts of the application is not efficient. This is when a technique known as code splitting, also known as lazy loading, can be useful.

Code splitting is a technique which allows individual components to be loaded via their own file, apart from the global bundle.js file, and only when they are being used. For example, only when a user actually visits the “home page/aboutme” url will the component responsible for that page, and all its potential child components, be loaded. This is done by splitting your application’s code into chunks or individual chunk.js files. Using this technique would minimize the application’s bundle.js file allowing for faster loading. And every other part of your application, would only be loaded upon use. Let’s take a look at how this is implemented.

I’ll break up the entire code for the function into parts to better grasp what is happening.

import React, { Component } from 'react';const asyncComponent = (importComponent) => {
return class extends Component {
}
};

First off we create a function, called asyncComponent, the name can be anything but essentially lazy loading a component is loading it asynchronously. This function receives an argument, a function represented as importComponent. Again, this argument name is used because it will represent a function that does exactly that, imports the actual component. The asyncComponent function also returns a higher order component, or a component that wraps another component (in this case, the actual component that needs to be rendered) and does some process to it before returning it. The higher order component is an anonymous class component, and it is anonymous because its only responsibility is to potentially render the actual component that is needed.

import React, { Component } from 'react';const asyncComponent = (importComponent) => {
return class extends Component {
state = {
component: null
}

componentDidMount() {
importComponent().then(cmp => this.setState({
component: cmp.default
)};
);
}
}
};

Afterwards, we add a state to the higher order component that will keep track of a potential component needing to be returned or rendered. Once the higher order component mounts, it will execute the function that is passed in as an argument to the asyncComponent function, importComponent. This function will return a promise (named “cmp” in this example) that will contain a property called default which will store the actual component that was imported by the execution of the importComponent function. Afterwards, the component needing to be rendered will be stored in state.

import React, { Component } from 'react';const asyncComponent = (importComponent) => {
return class extends Component {
state = {
component: null
}

componentDidMount() {
importComponent().then(cmp => this.setState({
component: cmp.default
)};
);
}
render() {
const C = this.state.component;
return C ? <C {...this.props}/> : null;
}
}
};export default asyncComponent;

At this point, the component that is stored inside the promise is set to state. Then, inside the render method, the potential component defined inside the state, is assigned to the C variable, short for Component. And if there is an actual component set to state, then any props received by the higher order component is passed down to the actual component, otherwise null is returned.

Now that the asyncComponent function is defined with its returning higher order component, it needs to be implemented. One example where it makes sense to implement this is when using Routes. So for example:

* App.jsimport React from 'react';
import HomePage from './components/HomePage';
import AboutMe from './components/AboutMe';
const App = () => {
return (
<div>
<Route path="/aboutme" component={AboutMe}/>
<Route path="/" component={HomePage}/>
</div>
);
}

In this example we have a standard way of loading certain components, particularly, the AboutMe component will render whenever you visit the “/aboutme” url. If a user does not visit this page, then including the code that handles this component, and all potential child components is not efficient.

So:

* App.jsimport React from 'react';
import { Route } from 'react-router-dom';
import HomePage from './components/HomePage';
import asyncComponent from './hoc/asyncComponent';
const AsyncAboutMe = asyncComponent(() => import('./components/AboutMe'));const App = () => {
return (
<div>
<Route path="/aboutme" component={AsyncAboutMe}/>
<Route path="/" component={HomePage}/>
</div>
);
}

Now we no longer import the actual component directly, which would include its code into the global bundle.js file, instead we import the asyncComponent function that will execute an anonymous function (importComponent argument) that will import the component. By doing it this way, webpack creates an individual file for the component in the form of a chunk.js file which is loaded when needed. The function, importComponent, then executes the import function which dynamically imports the component. Finally, the route for “/aboutme” now points to the AsyncAboutMe variable which will either be null or contain the AboutMe component if the route is visited.

This all might seem very involved and so there is another approach to lazy loading which was later introduced, involving the lazy method on the React object. And it work like this:

* App.jsimport React, { Suspense } from 'react';
import { Route } from 'react-router-dom';
import HomePage from './components/HomePage';
const AboutMe = React.lazy(() => import('./components/AboutMe'));
const App = () => {
return (
<div>
<Route path="/aboutme" render={() => {
return (
<Suspense fallback={someHTML}>
<AboutMe />
</Suspense>
);
}
}/>
<Route path="/" component={HomePage}/>
</div>
);
}

In this version, the lazy method substitutes the asyncComponent function receiving an anonymous function that invokes the import function. However, to use the asynchronously loaded component assigned to the AboutMe variable, we need to import the Suspense component which will wrap the actual AboutMe component. Also, the route to “/aboutme” utilizes the render method returning an anonymous function that returns the Suspense component with the actual component needing rendering as its child component. Also to note, the Suspense component takes a “fallback” prop which is used by React to render some HTML while the actual component is rendered. That HTML can be another component which is rendered first and immediately when the Suspense component is mounted, or it can be simply be some standalone HTML, for example a p tag with some text.

With this, you can now split your code into portions allowing for greater efficiency. And to see your asynchronous component loading in action, you can take a look in the developer tools under the “Network” tab, and when loading a component asynchronously, (visiting a page that is loaded using code splitting) you can see that a chunk.js file will appear which will contain the code for the loaded component. You can also read more on this in the React docs.

https://reactjs.org/docs/code-splitting.html

--

--

Uriel Rodriguez
The Startup

Flatiron School alumni and Full Stack web developer.