Loading components asynchronously in React app with an HOC
Arpy Vanyan
Posted on January 24, 2018
In the era of single-page applications, you can write (almost) any web app with your favorite React. Some apps, in fact, can be really huge! Imagine you are developing a Facebook clone… Pretty big, huh?
Now, as you might know, when your Webpack configuration does its magic on your code, it generates a single bundle.js file, that contains all the code needed on the front end. It includes all your components in addition to a lot of additional building blocks. So, the bigger the app, the larger the file. And, of course, big files take longer to download. Thus, the first page load slows down. And, in fact, the user might never visit a lot of the loaded pages and never see a lot of components.
It’s considered a good practice to break down the initial bundle.js file into chunks and load components upon request. Thus, the pages the user does not have the intention to visit and the components that will never be rendered will never be loaded.
There are a lot of different approaches to do this. I’ll share the one that involves HOCs :)
What is an HOC?
A higher-order component (HOC) is an advanced technique in React for reusing component logic. HOCs are not part of the React API, per se. They are a pattern that emerges from React’s compositional nature.
Think of an HOC as a wrapper around your components, that applies some common state and behavior alternations to them when they are created. An HOC is basically a function, that takes a component, and returns another component. You can use them as normal components in your jsx. Here’s the detailed documentation of Higher-order Components.
So what we’re going to do, is that we’ll define an HOC and apply it to those components that we want to be loaded asynchronously.
The Async Component HOC
Let’s create a file that will contain our HOC. We’ll call it asyncComponent.js. I like to have a separate folder named “hoc” in my project for holding all the higher-order components.
Here are the file contents:
//hoc/asyncComponent.js
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;
As you can see, it simply returns a function that receives another function and returns an anonymous class extended from the React Component. So, basically, our asyncComponent is a function that returns a component.
Now, importComponent is a function, that simply returns a component import. It might look something like this:
const impFn = () => {
return import('./components/Card');
}
Every time importComponent is called, React will try to import the component. It will download a chunk.js file containing the imported component.
Using asyncComponent
Let’s see how we can use this component and what what will happen if we do so. We’ll try to use it in another functional component as an example.
//components/Container.js
import React from 'react';
import asyncComponent from '../../hoc/asyncComponent';
const AsyncButton = asyncComponent(() => {
return import('../Button');
});
const container = () => {
return (
<div>
<h1>Here goes an async loaded button component</h1>
<AsyncButton/>
</div>
);
};
export default container;
Here, instead of using the < Button /> component in our DOM, we define a new component called AsyncButton. Knowing how we have defined the asyncComponent, we can guess that AsyncButton will be assigned a new type of Component. But what happens when it is added to the DOM? The answer is in the asyncComponent.
Apparently, when the AsyncButton is mounted (see componentDidMount), it calls our importComponent function. In our case, it will import and return the Button component. Until the import is done, the rendered DOM will be empty. When the missing component is loaded via a chunk file download, it will be added to the AsyncButton component’s state and the latter will re-render. Now, our async component will simply render the downloaded Button component with passed props.
And that’s it. We have made our Button component be fetched only if it is actually mounted ;)
Routing with Async Components
When you have a lot of container components (aka pages) in your app, it would be reasonable to initially load only the pages that are most likely to be visited and fetch the rest asynchronously. Our asyncComponent is just perfect for that. You’ll need to use it exactly like we did with the Button before.
Here’s a simple example to play with. Assume we have all our routing in a separate file with only 2 routes defined for simplicity. The home page that is initially loaded, and the user profile page that is not guaranteed to be visited.
//Routes.js
import React, {Component} from 'react';
import {Route, Switch} from 'react-router-dom';
import HomePage from './containers/HomePage';
const AsyncProfilePage = asyncComponent(() => {
return import('./containers/ProfilePage');
});
class Routes extends Component {
render() {
return (
<Switch>
<Route exact path='/' component={HomePage}/>
<Route exact path='/profile' component={AsyncProfilePage}/>
</Switch>
);
}
}
export default Routes;
Thus, the code for user profile page will be downloaded only if the user clicks on a link in home page that displays the desired page.
Hope you learned something new here, and happy Reactive coding! 🤘
Posted on January 24, 2018
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.