Type to generate custom UI components with AI

Type to generate UI components from text

OR

Browse thousands of MUI, Tailwind, React components that are fully customizable and responsive.

Explore Components

Ultimate Guide To Enhanced Performance with React Lazy

In the past, sophisticated React applications often had massive bundle sizes, negatively impacting performance and user experience. Developers sought to address this issue through third-party libraries, which offered tools for lazy loading React components, effectively reducing bundle sizes. However, the landscape changed with the release of React version 16.6, which introduced built-in features tailored to optimize and streamline lazy loading in React components. Among these features, React `lazy` stands out for its ability to simplify the logic to lazy load a component, providing a more straightforward approach for developers.

With that in mind, this article will delve into React lazy, code-splitting in React, exploring the differences between dynamic import and regular import, delving into advanced techniques with React `lazy,` and outlining best practices for using this feature. Let’s dive right in.

What is React Lazy?

The `lazy` method is a function supported by React to defer loading a component until it is rendered for the first time. It optimizes the performance of a React application by breaking a component’s code into smaller chunks and asynchronously loading these chunks. This approach is vital in complex applications, where loading all the components simultaneously will significantly affect the page load time.

Furthermore, the `React.lazy()` API, also known as the Application Programmable Interface takes a single argument, a `load` function, and returns a module with a default export containing a React component. This `load` function must call a dynamic import which returns a promise that eventually resolves to the React component. Consider the following syntax when using the `lazy` function:

import { lazy } from 'react';

const PreviewUI = lazy(() => import('./PreviewUI.js'));

Understanding Code-splitting with React Lazy: An overview

Initially, JavaScript-based applications utilized bundlers like WebpackRollup, and Browserify to consolidate imported modules into a single bundle. This bundle equips webpages with the required JavaScript functionality. However, as the app’s complexity and size increased, the resulting code bundle grew heavier, impacting page load times. The advent of ES6 introduced code-splitting capabilities to bundlers, offering a solution to manage code bundles more efficiently.

Code-splitting is a feature that allows developers to break large bundles of JavaScript code into smaller chunks, thereby facilitating better control over resource load prioritization. Code-splitting ensures optimal performance of React applications by dynamically loading the necessary resources the user needs during runtime. Moreover, what does it mean to `lazy load` resources in React? Let’s discuss that in the subsequent section.

Before we delve in, visit Purecode AI and explore how our AI-powered code automation platform can help you improve your workflow. It houses a library of UI components that you can customize to suit your React project needs.

What is Lazy Loading

In the past, components and their features were loaded immediately at runtime. This method, known as eager loading, allowed the browser to cache all the contents of the application, which ensured smoother navigation within and across its pages. However, this approach lost its effectiveness with larger applications because the load time increased significantly, thus diminishing the user experience. Introducing lazy loading addressed the challenges posed by eagerly loading components.

React lazy loading is a design pattern and optimization technique that involves loading critical chunks of an application’s user interface while deferring loading the remaining code chunk in the background. This approach benefits applications with numerous features that can negatively impact load time if initialized immediately. Thanks to lazy loading, only the required React elements are initialized at runtime. Consider the following code example:

import React from 'react';

// Eager Loaded Component
import EagerLoadedComponent from './EagerLoadedComponent';

// Lazy Loaded Component
const LazyLoadedComponent = React.lazy(() => import('./LazyLoadedComponent'));

function App() {
  return (
    <div>
      <h1>Eager Loading Example</h1>
      <EagerLoadedComponent />
      
      <h1>Lazy Loading Example</h1>
      <React.Suspense fallback={<div>Loading...</div>}>
        <LazyLoadedComponent />
      </React.Suspense>
    </div>
  );
}

export default App;

To learn more about React lazy loading, check out the tutorial video below:

How to Lazy Load Components

React provides two native features that make it easy to perform code-splitting and lazy loading in React. These features include the following:

  • React lazy function

  • React suspense component

Having discussed React lazy in a previous section, let’s briefly explore the Suspense API. The Suspense API is a component for wrapping lazy-loaded components, facilitating their asynchronous loading. Additionally, it provides the `fallback` prop, enabling the display of a loading indicator while waiting for the lazy loaded widget to render. This API plays a crucial role in ensuring the `lazy` API successfully defers the loading of specific components until required for the page’s UI.

Leveraging Suspense for Lazy Loading

React allows us to leverage the Suspense API for suspending the loading of lazily loaded components and displaying a fallback component while waiting for the component to load. This functionality allows us to implement lazy loading, as demonstrated below:

// Homepage.jsx

import React from "react";

function Homepage() {
  return (
    <div>
      <h1>My Homepage</h1>
    </div>
  );
}

export default Homepage;
import React, { lazy, Suspense } from "react";
const Homepage = lazy(() => import("./Homepage"));

function Fallback() {
  return (
    <div
      style={{
        height: "100vh",
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
      }}
    >
      <p>Loading...</p>
    </div>
  );
}

function App() {
  return (
    <Suspense fallback={<Fallback />}>
      <Homepage />
    </Suspense>
  );
}

export default App;

In the code example above, the Suspense API was used to suspend the lazy-loaded `Homepage` component and show a fallback loading UI while the lazy component renders, as illustrated below:

Benefits of Lazy Loading

Lazy loading offers numerous merits vital to the overall performance, user experience, and efficiency of React apps. These include the following:

  • Improved user experience: Lazy loading ensures relevant content displays promptly or presents a loading indicator, keeping the users engaged while the content renders and enhancing the user experience.

  • Scalability: Lazy loading simplifies the breakdown of large applications into smaller, more manageable chunks. This approach facilitates better code organization and maintenance.

  • Preserve system resources: Lazy loading guarantees that only the required resources are loaded, conserving memory and bandwidth.

  • Improved page load: Implementing lazy loading to load crucial components upfront and defer loading non-essential components, is essntial to markedly enhancing the page load time.

Differentiating Dynamic Import from Regular Import

Static and dynamic imports are the two different ways in JavaScript to load modules. However, they differ in their functionalities, as illustrated by the table below:

Dynamic ImportStatic/Regular Import
It is used to asynchronously and dynamically load modules.It is used to load modules required upfront.
Dynamic import is evaluated and resolved at runtime.Static import is evaluated and resolved at compile time.
It is achieved using the import() function.It is done using the import declaration.

Advanced Techniques with React Lazy

Code-splitting with React `lazy` encompasses strategies for optimizing component loading and enhancing application performance. These strategies include the following:

  • Lazy loading of multiple components

  • Route-based lazy loading

  • Handling loading error

Lazy Loading Multiple Components

In an earlier section, we employed `Suspense` to suspend loading for a single lazy component. Nevertheless, the versatility of the `Suspense` API allows us to wrap multiple lazy components as well. This functionality is vital for deferring the loading of numerous lazy components, ensuring only one lazy-loaded component gets rendered per section or page of the user interface. Let’s explore this further through the following example:

// Hero.jsx

import React from "react";

function Hero() {
  return (
    <div>
      <h1>Hero Section</h1>
    </div>
  );
}

export default Hero;
// Services.jsx

function Services({ value }) {
  return (
    <div
      style={{
        height: "100vh",
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        fontSize: "3rem",
      }}
    >
      <p>Hy, {value} this is the service section</p>
    </div>
  );
}

export default Services;
// About.jsx

function About() {
  return (
    <div
      style={{
        height: "100vh",
      }}
    >
      <h2>About</h2>
      <p>Lorem ipsum dolor sit amet consectetur adipisicing elit.</p>
    </div>
  );
}

export default About;
import React, { lazy, Suspense, useEffect, useState } from "react";
const Hero = lazy(() => import("./Hero"));
const Services = lazy(() => import("./Services"));
const About = lazy(() => import("./About"));

function Fallback() {
  return (
    <div
      style={{
        height: "100vh",
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
      }}
    >
      <p>Loading...</p>
    </div>
  );
}

function App() {
  const [currentSection, setCurrentSection] = useState(0);

  useEffect(() => {
    if (currentSection < 2) {
      let timeoutId;
      timeoutId = setTimeout(() => {
        setCurrentSection((prev) => prev + 1);
      }, 2000);

      return () => clearTimeout(timeoutId);
    }
  }, [currentSection]);

  return (
    <Suspense fallback={<Fallback />}>
      {currentSection === 0 ? (
        <Hero />
      ) : currentSection === 1 ? (
        <Services />
      ) : currentSection === 2 ? (
        <About />
      ) : null}
    </Suspense>
  );
}

export default App;

In the aforementioned example, Suspense was utilized to suspend multiple lazy components. These components were then rendered dynamically based on state updates, as demonstrated below:

Route-based Lazy Loading

Route-based components automatically load when a user navigates to a specific Universal Resource Locator (URL). However, in conventional setups, such as when a user first accesses an application and lands on the `Home` page, React loads all the route components irrespective of their relevance to that particular page. This indiscriminate loading process prolongs the page loading time, negatively impacting the user experience. Consider the example below:

// About.jsx

import Navbar from "./Navbar";

function About() {
  return (
    <div
      style={{
        height: "100vh",
      }}
    >
      <Navbar />
      <h2>About</h2>
      <p>Lorem ipsum dolor sit amet consectetur adipisicing elit.</p>
    </div>
  );
}

export default About;
// Home.jsx

import Navbar from "./Navbar";

import React from "react";

function Home() {
  return (
    <div>
      <Navbar />
      <h1>Home page</h1>
    </div>
  );
}

export default Home;
// Services.jsx

import Navbar from "./Navbar";

function Services({ value }) {
  return (
    <div>
      <Navbar />
      <h2>Services page</h2>
      <p>Hy, {value} this is the service section</p>
    </div>
  );
}

export default Services;
// Navbar.jsx

import { Link } from "react-router-dom";

const Navbar = () => (
  <nav>
    <ul
      style={{
        listStyle: "none",
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        padding: "0.75rem 1rem",
        columnGap: "1rem",
      }}
    >
      <li>
        <Link to="/">Home</Link>
      </li>
      <li>
        <Link to="/services">Services</Link>
      </li>
      <li>
        <Link to="/about">About</Link>
      </li>
    </ul>
  </nav>
);

export default Navbar;
import React from "react";
import {
  createBrowserRouter,
  createRoutesFromElements,
  RouterProvider,
  Route,
  Link,
} from "react-router-dom";
import Home from "./Home";
import Services from "./Services";
import About from "./About";

const router = createBrowserRouter(
  createRoutesFromElements(
    <Route path="/">
      <Route index element={<Home />} />
      <Route path="services" element={<Services />} />
      <Route path="about" element={<About />} />
    </Route>
  )
);

function App() {
  return <RouterProvider router={router} />;
}

export default App;

The example above employs static import to promptly initialize the route components. While suitable for small-scale applications, this approach leads to noticeable delays in loading time with complex applications, affecting overall performance.

Therefore, optimizing bundle size for page routes is vital for performance and user experience. React allows us to also perform route-based code-splitting and lazy-load route components, ensuring only the required component is loaded and the rest deferred. Let’s tweak the example above to illustrate this:

import React, { lazy, Suspense } from "react";
import {
  createBrowserRouter,
  createRoutesFromElements,
  RouterProvider,
  Route,
  Link,
} from "react-router-dom";

const Home = lazy(() => import("./Home"));
const Services = lazy(() => import("./Services"));
const About = lazy(() => import("./About"));

const router = createBrowserRouter(
  createRoutesFromElements(
    <Route path="/">
      <Route index element={<Home />} />
      <Route path="services" element={<Services />} />
      <Route path="about" element={<About />} />
    </Route>
  )
);

function Fallback() {
  return (
    <div
      style={{
        height: "100vh",
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
      }}
    >
      <p>Loading...</p>
    </div>
  );
}

function App() {
  return (
    <Suspense fallback={Fallback}>
      <RouterProvider router={router} />
    </Suspense>
  );
}

export default App;

The code example above utilizes the React `lazy` and `Suspense` API to only load and render what’s necessary for a particular page’s UI. Below is an illustration of this:

Handling Loading Error

In a preceding section, we established that the `lazy` callback function returns a Promise that resolves to the lazy component upon successful loading or gets rejected, leading to the application crashing. Rejection of the returned Promise could stem from network failure, file path errors, etc. Maintaining a dependable and seamless user experience mandates handling exceptions within the component tree. Therefore, providing an error boundary ensures catching thrown errors and handling them effectively.

An uncaught error that crashes the application
import React, { lazy, Suspense } from "react";
import {
  createBrowserRouter,
  createRoutesFromElements,
  RouterProvider,
  Route,
} from "react-router-dom";
import { ErrorBoundary } from "react-error-boundary";

const Home = lazy(() => import("./Home"));
const Services = lazy(() => import("./Services"));
const About = lazy(() => import("./About"));

const router = createBrowserRouter(
  createRoutesFromElements(
    <Route path="/">
      <Route
        index
        element={
          <ErrorBoundary FallbackComponent={Fallback}>
            <Home />
          </ErrorBoundary>
        }
      />
      <Route
        path="services"
        element={
          <ErrorBoundary FallbackComponent={Fallback}>
            <Services />
          </ErrorBoundary>
        }
      />
      <Route
        path="about"
        element={
          <ErrorBoundary FallbackComponent={Fallback}>
            <About />
          </ErrorBoundary>
        }
      />
    </Route>
  )
);

function Fallback({ error, resetBoundary }) {
  return (
    <div
      style={{
        height: "100vh",
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
      }}
    >
      <div>
        <p>Something went wrong</p>
        <pre style={{ color: "red", whiteSpace: "break-spaces" }}>
          {error?.message}
        </pre>
      </div>
    </div>
  );
}

function App() {
  return (
    <ErrorBoundary FallbackComponent={Fallback}>
      <RouterProvider router={router} />
    </ErrorBoundary>
  );
}

export default App;
The Fallback UI after catching and managing exceptions with an error boundary

Use case Scenarios for React Lazy

React `lazy` is utilized in various use cases where performance is concerned by splitting your entire app bundle into smaller, more manageable chunks and loading them on demand. These use cases include the following:

  • Large applications: Lazy loading is vital in large-scale applications with numerous components. It improves the initial loading time by dynamically loading only the necessary components upfront and deferring the loading of non-essential components until when needed.

  • Multi-step form: Lazy loading is utilized in a multi-step form to load components needed at each step as the user progresses instead of loading all the form components upfront. This method improves performance and loading time, especially for forms with numerous steps.

  • Tabbed Interfaces: User interfaces with multiple tabs or sections utilize lazy loading to load the content of each tab when needed. This approach ensures an optimal loading process and a smoother user experience.

Best Practice for Utilizing React Lazy

It is crucial to consider best practices for utilizing React `lazy` to ensure a seamless and effective implementation. These include the following:

  • Use suspense for fallback

  • Employ an error boundary for graceful error handling

  • Test performance with various devices and networks

  • Identify suitable components

  • Group components to optimize bundle size

Use Suspense for Fallback

Wrap lazy components with the `Suspense` component and provide a fallback UI to display while the content is lazily loaded. This approach enhances the user experience by providing visual feedback while the component loads.

Employ an Error Boundary

Wrap lazy-loaded components with proper error boundaries to catch and manage exceptions that may occur during loading. This practice improves user experience by preventing the application from crashing. In addition, the React team recommends and I quote:

Currently, there is no way to write an error boundary as a function component. However, you don’t have to write the error boundary class yourself. For example, you can use react-error-boundary instead.

Test Performance with Various Devices and Networks

It is vital to test the performance of your application against a range of devices and platforms, both locally and in production. Monitor network requests to identify and optimize any potential bottlenecks.

Identify Suitable Components

Analyze and identify components that are suitable for lazy loading. Typically, you’ll want to lazy load components based on routing and user interaction.

Group Component to Optimize Bundle Size

Group related components into separate bundles. This approach optimizes the loading process by reducing the requests made to the server.

Final Thoughts

React `lazy` API is a pivotal tool in the arsenal of modern web developers, facilitating efficient code-splitting and lazy loading components. Its ability to partition code bundle into manageable chunks and asynchronously load these segments contributes to optimizing performance and maintainability in large-scale applications. However, while the `lazy` API offers several benefits, it is necessary to identify components and context that warrant the unique functionalities of the React `lazy` API. By carefully leveraging it, developers can harness the full capabilities of React `lazy` to improve performance and ensure a smooth user experience.

Finally, visit Purecode AI, a platform with state-of-the-art AI-powered code automation built to help you improve your workflow. Explore our library of UI customizable components tailored to suit your project requirements.

Recommended Resources

If this article made an impact, consider reading other great articles from our blog to elevate your React knowledge:

Ofili Chukwuemeka Timothy

Ofili Chukwuemeka Timothy