In this tutorial, we will learn about lazy loading in React.js and implement a simple web application that utilizes this.

Lazy loading refers to loading something only when it’s required by making requests dynamically. In React.js it is used to import components and associated resources only when they need to be rendered. The goal of lazy loading is to get faster initial load times.

Users only load the code they need for the features they are using. This leads to fast load times and more efficient use of resources.

React.lazy

The React.lazy function lets you render a dynamic import as a regular component. This helps reduce the bundle size to delay loading components that aren’t used during the initial render.

This way, as your website gets bigger and more complex, it still has fast loading times because it only loads the parts that are actually required to display the current state of the page.

React.lazy runs its callback function that usually contains a dynamic import statement only when that component is needed to be rendered in the App.

import React from 'react'

// This component is loaded dynamically
const SomeComponent = React.lazy(() => import('./SomeComponent'))

Note that rendering lazy components requires that there’s a <React.Suspense> component higher in the rendering tree. This is how you specify a loading indicator.

This will automatically load the bundle containing the OtherComponent when this component is first rendered.

React.lazy takes a function that must call a dynamic import(). This must return a Promise which resolves to a module with a default export containing a React component.

React.Suspense

React.Suspense lets you specify the loading indicator, In case some components in the tree below it are not yet ready to render. So in the event that the component requested hasn’t been received fully, an alternate element can be displayed.

It works similar to the alt attribute for the HTML img tag, but the fallback here can be a Component or a JSX element.

import React from 'react'

// This component is loaded dynamically
const OtherComponent = React.lazy(() => import('./OtherComponent'))

function MyComponent() {
  return (
    // Displays <Spinner> until OtherComponent loads
    <React.Suspense fallback={<Spinner />}>
      <div>
        <OtherComponent />
      </div>
    </React.Suspense>
  )
}

Note that lazy components can be deep inside the Suspense tree — it doesn’t have to wrap every one of them. The best practice is to place <Suspense> where you want to see a loading indicator, but to use lazy() wherever you want to do code splitting.

The Multiple Tabs Example

Now that we have understood what React.lazy and React.Suspense are meant to do, let’s code a washed-down version of our multiple tabs example.

In our application, there will be 4 buttons, clicking on which, will change the tab that is rendered on the page.

We will use create-react-app to create our application.

create-react-app lazy-loading

This will set up a React application for us.

Next, we will move to our project directory.

cd lazy-loading

File Structure

Our file structure will be mostly the same as create-react-app made for us. But we will make some changes in the src directory of our project.

  • First, we will delete all the unnecessary files in our directory, keeping only index.js, index.css, App.js and App.css.
  •  Now we will create a tabs folder that will store files for components to render for different tabs.

Note:- You can keep any/all files, and not delete them if you want.

Tabs

Now, we will create separate files to store components for each different tab. In this tutorial, we will just put a heading that prints the tab we are on.

const Tab = () => {
    return (
        <h1>Tab 1</h1>
    )
}
export default Tab

In the tabs folder we created, create 4 files with the names Tab_1.js, Tab_2.js, Tab_3.js and Tab_4.js. In those files put the above code and change the tab number that it prints, accordingly.

App.js

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

For our App component, we will import lazy, Suspense, and useState from “react”. We also have another import that imports our CSS file.

App Component

const Tab1 = lazy(() => import('./tabs/Tab_1'))
const Tab2 = lazy(() => import('./tabs/Tab_2')) 
const Tab3 = lazy(() => import('./tabs/Tab_3'))
const Tab4 = lazy(() => import('./tabs/Tab_4'))
const tabs = [Tab1, Tab2, Tab3, Tab4]

First, we lazily import all our tabs and store them in their respective variables. We then store those tabs in an array named Tabs.

Note :- Our App does not actually import those tabs in the file yet.

const [currentTab, setCurrentTab] = useState(1)

We also initialize a state variable that stores number for the tab we are on.

const Tab = tabs[currentTab - 1]

We then set the Tab variable which stores a component from the array based on the current tab.

return (
    <div className="App">
        <button value="1" onClick={handleClick}>1</button>
        <button value="2" onClick={handleClick}>2</button>
        <button value="3" onClick={handleClick}>3</button>
        <button value="4" onClick={handleClick}>4</button>
        <Suspense fallback={<h1>Loading...</h1>}>
            <Tab />
        </Suspense>
    </div>
  )

Our App Component renders 4 buttons with a click event handler handleClick that sets the state currentTab to the value of the clicked button. We put the Tab component inside a Suspense component with the fallback of a heading that says Loading. So, when we click a button, the Suspense component displays the fallback until all its child components have been imported, in this case, the Tab component.

const handleClick = ({target}) => {
    setCurrentTab(Number(target.value))
  }

Every time the state is changed by handleClick, the component is re-rendered changing the tabs.

Our whole component put together looks like this.

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

const App = () => {
  const Tab1 = lazy(() => import('./tabs/Tab_1'))
  const Tab2 = lazy(() => import('./tabs/Tab_2')) 
  const Tab3 = lazy(() => import('./tabs/Tab_3'))
  const Tab4 = lazy(() => import('./tabs/Tab_4'))
  const tabs = [Tab1, Tab2, Tab3, Tab4]

  const [currentTab, setCurrentTab] = useState(1)

  const handleClick = ({target}) => {
    setCurrentTab(Number(target.value))
  }

  const Tab = tabs[currentTab - 1]

  return (
    <div className="App">
        <button value="1" onClick={handleClick}>1</button>
        <button value="2" onClick={handleClick}>2</button>
        <button value="3" onClick={handleClick}>3</button>
        <button value="4" onClick={handleClick}>4</button>
        <Suspense fallback={<h1>Loading...</h1>}>
            <Tab />
        </Suspense>
    </div>
  )
}

export default App

Below are some screenshots of the Network Tab.

network tab

Initially, only Tab 1 is loaded.

tab 2

After clicking on button 2, Tab 2 is loaded.

tab 4

And after clicking on button 4, tab 4 is loaded.

Note :- Clicking on button 2 again, will not send another request because it has already been loaded.

Summary

  • The React.lazy function lets you render a dynamic import as a regular component. This helps reduce the bundle size to delay loading components that aren’t used during the initial render.
  • React.Suspense lets you specify the loading indicator in case some components in the tree below it are not yet ready to render.
  • The best practice is to place <Suspense> where you want to see a loading indicator, but to use lazy() wherever you want to do code splitting.