React Lazy Loading Component with Lazy and Suspense

In this article, we are go to build a simple profile app with a combination of React.lazy(), Suspense and ErrorBoundary component. Don’t forget to check the complete working codes in the end of this page.

Table of content

Why we need Lazy Loading?

By default the App bundle size grows with your application grow and impact on application performance. React introduced a lazy() function which can create multiple bundles dynamically at run time.

What is Lazy Loading in React?

import Expertise from './containers/Expertise'
import Testimonial from './containers/Testimonial'
import Portfolio from './containers/Portfolio'

This is an example of the regular component. The React.lazy() is a function that helps to dynamic import a regular component.

import { lazy } from 'react'

const Portfolio     = lazy(() => import('./containers/Portfolio'))
const Testimonial   = lazy(() => import('./containers/Testimonial'))
const Expertise     = lazy(() => import('./containers/Expertise'))

The lazy() function uses the dynamic import() and must return the Promise to resolve the component.

React Lazy and Suspense Example

Let’s go through an example to understand the lazy() function. We are going to create simple profile page with portfolio, testimonials and area of expertise. We have used the tailwindcss for the frontend, if you are not familiar with setup the tailwindcss then take a look at how to setup tailwindcss with React.

Let’s create the profile page with CRA( create-react-app).

npx create-react-app profile-page

We require three components for our profile page i.e. Image.js, Blockquote.js and Tag.js and create them under src/components directory. You can copy the codes from the below and paste them into each file.

// Image.js
const Image = ( props ) => {

    return (
        <svg className={`mr-6 mb-6 bd-placeholder-img bd-placeholder-img-lg d-block w-100 rounded`} width="100" height="100" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: First slide" preserveAspectRatio="xMidYMid slice" focusable="false">
            <title>Placeholder</title>
            <rect width="100%" height="100%" fill="#777"></rect>
            <text x="30%" y="50%" fill="#555" dy=".3em">Image</text>
        </svg>
    )
}

export default Image;

// Blockquote.js
const Blockquote = ({ quote, name, role }) => {
    return (
        <div>
            <blockquote className="mb-4">
                <p className="text-sm">"{quote}"</p>
            </blockquote>
            <div className="font-small text-sm"><span className="text-cyan-600">{name}</span> <span className="text-gray-500">{role}</span></div>
        </div>
    )
}

export default Blockquote;

// Tag.js
const Tag = ( props ) => {
    return (
        <span { ...props } className="px-2 rounded text-sm bg-pink-light text-gray-700 mr-2">{ props.children }</span>
    )
}

export default Tag;

Now let’s create three files i.e. Portfolio.js, Testimonial.js and Expertise.js under src/containers directory.

// Portfolio.js
import Image from './../components/Image'

const Portfolio = () => {

    return (
        <div className="flex flex-wrap">
            { [...Array(12)].map((v, i) => (
                <Image key={i} />
            ))}
        </div>
    );
}

export default Portfolio;
// Testimonial.js
import Blockquote from './../components/Blockquote'

const Testimonial = ( props ) => {
    return <Blockquote {...props} />
}

export default Testimonial;
// Expertise.js
import Tag from './../components/Tag'

const Expertise = ({ expertise }) => {
    return (
        <>
            {expertise.map( expert => <Tag key={expert}>{ expert }</Tag> )}
        </>
    )
}

export default Expertise;

The regular way to use these containers is the following.

import React from 'react';
import './App.css';

import Expertise from './containers/Expertise'
import Testimonial from './containers/Testimonial'
import Portfolio from './containers/Portfolio'

But we are not going to use this way. We will use the React.lazy() function to dynamic import these containers to run time.

import React, { lazy } from 'react';
import './App.css';


const Portfolio     = lazy(() => import('./containers/Portfolio'))
const Testimonial   = lazy(() => import('./containers/Testimonial'))
const Expertise     = lazy(() => import('./containers/Expertise'))

But this is not enough, you will get an error while using the lazy() function without Suspense component.

Error: A react component suspended while rendering, but no fallback UI was specified. Add a Suspense fallback= component higher in the tree to provide a loading indicator or placeholder to display.

The Suspense component allows us to fallback some content like loading indicator while we are waiting for loading the component.

First import the Suspense component from React.

import React, { lazy, Suspense } from 'react'
import './App.css';


const Portfolio     = lazy(() => import('./containers/Portfolio'))
const Testimonial   = lazy(() => import('./containers/Testimonial'))
const Expertise     = lazy(() => import('./containers/Expertise'))


function App() {
    return(
        ....
        ....
        ....

        <Suspense fallback={<div>Loading...</div>}>
            <div className="w-full md:w-2/3 lg:w-2/3 bg-grey">
                <div className="p-6 md:p-8 lg:p-8 lg:border-b border-red-100">
                    <div className="portfolio">
                        <h2 className="text-base font-bold uppercase text-green mb-4">Portfolio</h2>
                        <Portfolio />
                    </div>
                </div>
                <div className="p-6 md:p-8 lg:p-8">
                    <div className="testimonials mb-8">
                        <h2 className="text-base font-bold uppercase text-green mb-2">Testimonials</h2>
                        <div>
                            { testimonials.map( (testimonial, i) => <Testimonial key={`t${i}`} {...testimonial} /> ) }
                        </div>
                    </div>
                    <div className="expertise">
                        <h2 className="text-base font-bold uppercase text-green mb-2">Area of Expertise</h2>
                        <div className="flex flex-wrap">
                            <Expertise expertise={expertise} />
                        </div>
                    </div>
                </div>
            </div>
        </Suspense>
        ....
        ....
        ....
    )
}

Don’t confused with the .... ( dots ), we just escaped some piece of codes so you better focused on the Suspense component and the lazy() function.

Now if you check the application load time under profiler or network tab.

Performance before lazy() function

After lazy loading component

Well, we haven’t finished yet, we need to take care of one more thing i.e. ErrorBoundary. We need to set Error boundary in case of offline or network failure.

Let’s create ErrorBoundary component and warp the Suspense component with parent component.

// ErrorBoundary.js
import React from 'react'

class ErrorBoundary extends React.Component {
    constructor(props) {
        super(props);
        this.state = {hasError: false};
    }
  
    static getDerivedStateFromError(error) {
        return {hasError: true};
    }
  
    render() {
        if (this.state.hasError) {
            return <p>Loading failed! Please reload.</p>;
        }
  
        return this.props.children;
    }
}

export default ErrorBoundary;
function App() {
    return(
        ....
        ....
        ....

        <ErrorBoundary>
            <Suspense fallback={<div>Loading...</div>}>
                ....
                ....
                ....
            </Suspense>
        </ErrorBoundary>
        ....
        ....
        ....
    )
}

We hope this article will help you to learn how to use React.lazy() and Suspense in React. If you like this article then please follow us on Facebook and Twitter.