Glad Chinda Full-stack web developer learning new hacks one day at a time. Web technology enthusiast. Hacking stuffs @theflutterwave.

Lazy-loading components in React 16.6

5 min read 1518

Code-splitting and lazy-loading React components with Suspense and React.lazy()

The new release of React 16.6 rolled in with some new features that can be used to add more power to React components with little amounts of effort.

Two of these new features are React.Suspense and React.lazy(), which make it very easy to apply code-splitting and lazy-loading to React components.

This article focuses on how these two new features can be used in React applications and the new potentials they open up to React developers.

Code-splitting

Writing JavaScript applications has evolved over the last few years. With the advent of ES6(modules), transpilers like Babel, and bundlers like Webpack and Browserify, JavaScript applications can now be written in a completely modular pattern for easy maintainability.

Usually, each module gets imported and merged into a single file called the bundle, and then the bundle is included on a webpage to load the entire app. However, as the app grows, the bundle size starts becoming too large and hence begins to impact page load times.

Bundlers like Webpack and Browserify provide support for code-splitting, which involves splitting the code into different bundles which can be loaded on demand (lazy-loaded) instead of being loaded all at once, thereby improving the performance of the app.

Dynamic Imports

One of the major ways of splitting code is using dynamic imports. Dynamic imports leverage on the import() syntax, which is not yet part of the JavaScript language standard but is still a proposal that is expected to be accepted soon.

Calling import() to load a module relies on JavaScript Promises. Hence, it returns a promise that is fulfilled with the loaded module or rejected if the module could not be loaded.

For older browsers, a polyfill like es6-promise should be used to shim Promise.

Here is what it looks like to dynamically import a module for an app bundled with Webpack:

When Webpack sees this syntax, it knows to dynamically create a separate bundle file for the moment library.

For React apps, code-splitting using dynamic import() happens on the fly if boilerplates like create-react-app or Next.js is being used.

However, if a custom Webpack setup is being used, then you need to check the Webpack guide for setting up code-splitting. For Babel transpiling, you also need the babel-plugin-syntax-dynamic-import plugin, to allow Babel parse dynamic import() correctly.

Code-splitting React components

Several techniques have been in use for code-splitting React components. A common approach is applying dynamic import() to lazy-load Route components for an application — this is usually referred to as route-based code-splitting.

However, there is a very popular package for code-splitting React components called react-loadable. It provides a higher-order component (HOC) for loading React components with promises, leveraging on the dynamic import() syntax.

Consider the following React component called MyComponent:

Here, the OtherComponent is not required until MyComponent is getting rendered. However, because we are importing OtherComponent statically, it gets bundled together with MyComponent.

We can use react-loadable to defer loading OtherComponent until when we are rendering MyComponent, thereby splitting the code into separate bundles. Here is the OtherComponent lazy-loaded using react-loadable.

Here, you see that the component is imported using the dynamic import() syntax and assigned to the loader property in the options object.

React-loadable also uses a loading property to specify a fallback component that will be rendered while waiting for the actual component to load.

You can learn more about what you can accomplish with react-loadable in this documentation.

Using Suspense and React.lazy()

In React 16.6, support for component-based code-splitting and lazy-loading has been added via React.lazy() and React.Suspense.

React.lazy() and Suspense are not yet available for server-side rendering. For server-side code-splitting, React Loadable should still be used.

React.lazy()

React.lazy() makes it easy to create components that are loaded using dynamic import() but are rendered like regular components. This will automatically cause the bundle containing the component to be loaded when the component is rendered.

React.lazy() takes a function as its argument that must return a promise by calling import() to load the component. The returned Promise resolves to a module with a default export containing the React component.

Here is what using React.lazy() looks like:

Suspense

A component created using React.lazy() only gets loaded when it needs to be rendered.

Hence, there is need to display some form of placeholder content while the lazy component is being loaded — possibly a loading indicator. This is exactly what React.Suspense was created for.

React.Suspense is a component that is meant for wrapping lazy components. You can wrap multiple lazy components at different hierarchy levels with a single Suspense component.

The Suspense component takes a fallback prop that accepts the React elements you want rendered as placeholder content while all the lazy components get loaded.

An error boundary can be placed anywhere above lazy components to show nice user experience if a lazy component fails to load.

I have created a very simple demo on CodeSandbox to demonstrate using React.lazy() and Suspense for lazy-loading components.

Here is what the miniature app code looks:

import React, { Suspense } from "react";
import Loader from "./components/Loader";
import Header from "./components/Header";
import ErrorBoundary from "./components/ErrorBoundary";

const Calendar = React.lazy(() => {
  return new Promise(resolve => setTimeout(resolve, 5 * 1000)).then(
    () =>
      Math.floor(Math.random() * 10) >= 4
        ? import("./components/Calendar")
        : Promise.reject(new Error())
  );
});

export default function CalendarComponent() {
  return (
    <div>
      <ErrorBoundary>
        <Header>Calendar</Header>

        <Suspense fallback={<Loader />}>
          <Calendar />
        </Suspense>
      </ErrorBoundary>
    </div>
  );
}

Here, a very simple Loader component has been created to use as fallback content for the lazy Calendar component. An error boundary has also been created to show a nice error when the lazy Calendar component fails to load.

I have wrapped the lazy Calendar import with another promise to simulate a delay of 5 seconds. To increase the chances of the Calendar component failing to load, I have also used a condition to either import the Calendar component, or return a promise that rejects.

The following screenshot shows a demo of what the component will look like when rendered.

Demonstration of lazy-loading component using React.lazy() and Suspense

Named exports

At the moment, React.lazy() does not support using named exports for React components.

If you wish to use named exports containing React components, then you need to reexport them as default exports in separate intermediate modules.

Let’s say you have OtherComponent as a named export in a module and you wish to load OtherComponent using React.lazy(), then you will create an intermediate module for reexporting OtherComponent as a default export.

Components.js

OtherComponent.js

Then you can now use React.lazy() to load OtherComponent from the intermediate module.

Lazy-loading routes

With React.lazy() and Suspense, it is now easy to perform route-based code-splitting without using any other external package. You can simply convert the route components of your app to lazy components and wrap all the routes with a Suspense component.

The following code snippet shows route-based code-splitting using the Reach Router library.

import React, { Suspense } from 'react';
import { Router } from '@reach/router';
import Loading from './Loading';

const Home = React.lazy(() => import('./Home'));
const Dashboard = React.lazy(() => import('./Dashboard'));
const Overview = React.lazy(() => import('./Overview'));
const History = React.lazy(() => import('./History'));
const NotFound = React.lazy(() => import('./NotFound'));

function App() {
  return (
    <div>
      <Suspense fallback={<Loading />}>
        <Router>
          <Home path="/" />
          <Dashboard path="dashboard">
            <Overview path="/" />
            <History path="/history" />
          </Dashboard>
          <NotFound default />
        </Router>
      </Suspense>
    </div>
  )
}

Conclusion

With the new React.lazy() and React.Suspense, code-splitting and lazy-loading React components has been made very easy.

You can start enjoying the new React features by updating to React 16.6.

Clap & Follow

If you found this article insightful, feel free to give some rounds of applause if you don’t mind.

You can also follow me on Medium (Glad Chinda) for more insightful articles you may find helpful. You can also follow me on Twitter (@gladchinda).

Enjoy coding…


Plug: LogRocket, a DVR for web apps

https://logrocket.com/signup/

LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single page apps.

Try it for free.

Glad Chinda Full-stack web developer learning new hacks one day at a time. Web technology enthusiast. Hacking stuffs @theflutterwave.

2 Replies to “Lazy-loading components in React 16.6”

  1. Hey! Thanks for your post!

    Using lazy routes, after updating the app, if you do not refresh the app on the browser, it will try to load an old version of the bundle. so error “Uncaught SyntaxError: Unexpected token <” come up. How do you prevent that?

Leave a Reply