Using React Loadable for Code Splitting by Components and Routes

Avatar of Kingsley Silas
Kingsley Silas on

In a bid to have web applications serve needs for different types of users, it’s likely that more code is required than it would be for one type of user so the app can handle and adapt to different scenarios and use cases, which lead to new features and functionalities. When this happens, it’s reasonable to expect the performance of an app to dwindle as the codebase grows.

Code splitting is a technique where an application only loads the code it needs at the moment, and nothing more. For example, when a user navigates to a homepage, there is probably no need to load the code that powers a backend dashboard. With code splitting, we can ensure that the code for the homepage is the only code that loads, and that the cruft stays out for more optimal loading.

Code splitting is possible in a React application using React Loadable. It provides a higher-order component that can be set up to dynamically import specific components at specific times.

Component splitting

There are situations when we might want to conditionally render a component based on a user event, say when a user logs in to an account. A common way of handling this is to make use of state — the component gets rendered depending on the logged in state of the app. We call this component splitting.

Let’s see how that will look in code.

See the Pen
React-Loadable
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

As a basic example, say we want to conditionally render a component that contains an <h2> heading with “Hello.” Like this:

const Hello = () => {
	return (
		<React.Fragment>
			<h2>Hello</h2>
		</React.Fragment>
	)
}

We can have an openHello state in the App component with an initial value of false. Then we can have a button used to toggle the state, either display the component or hide it. We’ll throw that into a handleHello method, which looks like this:

class App extends React.Component {
	state = {
		openHello: false
	}

	handleHello = () => {
		this.setState({ openHello: !this.state.openHello })
	}
	render() {
		return (
			<div className="App">
				<button onClick={this.handleHello}>
					Toggle Component
				</button>

				{
					this.state.openHello ?
						<Hello />
					: null
				}
			</div>
		);
	}
}

Take a quick peek in DevTools and take note the Network tab:

Now, let’s refactor to make use of LoadableHello. Instead of importing the component straight up, we will do the import using Loadable. We’ll start by installing the react-loadable package:

## yarn, npm or however you roll
yarn add react-loadable

Now that’s been added to our project, we need to import it into the app:

import Loadable from 'react-loadable';

We’ll use Loadable to create a “loading” component which will look like this:

const LoadableHello = Loadable({
	loader: () => import('./Hello'),
	loading() {
		return <div>Loading...</div>
	}
})

We pass a function as a value to loader which returns the Hello component we created earlier, and we make use of import() to dynamically import it. The fallback UI we want to render before the component is imported is returned by loading(). In this example, we are returning a div element, though we can also put a component in there instead if we want.

Now, instead of inputting the Hello component directly in the App component, we’ll put LoadableHello to the task so that the conditional statement will look like this:

{
	this.state.openHello ?
		<LoadableHello />
	: null
}

Check this out — now our Hello component loads into the DOM only when the state is toggled by the button:

And that’s component splitting: the ability to load one component to load another asynchronously!

Route-based splitting

Alright, so we saw how Loadable can be used to load components via other components. Another way to go about it is us ing route-based splitting. The difference here is that components are loaded according to the current route.

So, say a user is on the homepage of an app and clicks onto a Hello view with a route of /hello. The components that belong on that route would be the only ones that load. It’s a fairly common way of handling splitting in many apps and generally works well, especially in less complex applications.

Here’s a basic example of defined routes in an app. In this case, we have two routes: (1) Home (/) and (2) Hello (/hello).

class App extends Component {
	render() {
		return (
			<div className="App">
				<BrowserRouter>
					<div>
						<Link to="/">Home</Link>
						<Link to="/hello">Hello</Link>
						<Switch>
							<Route exact path="/" component={Home} />
							<Route path="/hello" component={Hello} />
						</Switch>
					</div>
				</BrowserRouter>
			</div>
		);
	}
}

As it stands, all components will render when a use switches paths, even though we want to render the one Hello component based on that path. Sure, it’s not a huge deal if we’re talking a few components, but it certainly could be as more components are added and the application grows in size.

Using Loadable, we can import only the component we want by creating a loadable component for each:

const LoadableHello = Loadable({
	loader: () => import('./Hello'),
	loading() {
		return <div>Loading...</div>
	}
})
const LoadableHome = Loadable({
	loader: () => import('./Home'),
	loading() {
		return <div>Loading...</div>
	}
})
class App extends Component {
	render() {
		return (
			<div className="App">
				<BrowserRouter>
					<div>
						<Link to="/">Home</Link>
						<Link to="/hello">Hello</Link>
						<Switch>
							<Route exact path="/" component={LoadableHome} />
							<Route path="/hello" component={LoadableHello} />
						</Switch>
					</div>
				</BrowserRouter>
			</div>
		);
	}
}

Now, we serve the right code at the right time. Thanks, Loadable!

What about errors and delays?

If the imported component will load fast, there is no need to flash a “loading” component. Thankfully, Loadable has the ability to delay the loading component from showing. This is helpful to prevent it from displaying too early where it feels silly and instead show it after a notable period of time has passed where we would expect to have seen it loaded.

To do that, our sample Loadable component will look like this;

const LoadableHello = Loadable({
	loader: () => import('./Hello'),
	loading: Loader,
	delay: 300
})

Here, we are passing the Hello component as a value to loading, which is imported via loader. By default, delay is set to 200ms, but we’ve set ours a little later to 300ms.

Now let’s add a condition to the Loader component that tells it to display the loader only after the 300ms delay we set has passed:

const Loader = (props) => {
	if (props.pastDelay) {
		return <h2>Loading...</h2>
	} else {
		return null
	}
}

So the Loader component will only show if the Hello component does not show after 300ms.

react-loader also gives us an error prop which we can use to return errors that are encountered. And, because it is a prop, we can let it spit out whatever we want.

const Loader = (props) => {
	if (props.error) {
		return <div>Oh no, something went wrong!</div>;
	} else if (props.delay) {
		return <h2>Loading...</h2>
	} else {
		return null;
	}
}

Note that we’re actually combining the delay and error handling together! If there’s an error off the bat, we’ll display some messaging. If there’s no error, but 300ms have passed, then we’ll show a loader. Otherwise, load up the Hello component, please!

That’s a wrap

Isn’t it great that we have more freedom and flexibility in how we load and display code these days? Code splitting — either by component or by route — is the sort of thing React was designed to do. React allows us to write modular components that contain isolated code and we can serve them whenever and wherever we want and allow them to interact with the DOM and other components. Very cool!

Hopefully this gives you a good feel for code splitting as a concept. As you get your hands dirty and start using it, it’s worth checking out more in-depth posts to get a deeper understanding of the concept.