DEV Community

Cover image for Lazy loading with React
Yohann Legrand
Yohann Legrand

Posted on • Edited on

Lazy loading with React

Photo by Priscilla Du Preez @unsplash

Read this article on my blog

Why you need lazy loading

Most of the time, you will have parts of your page that contain code and/or data for a component that won't be visible straight away, unless the user clicks somewhere or scroll the page. Loading all these resources can block the main thread and push how soon users will be able to interact with the page.

This can impact the performance of your website on metrics tools like Time to interactive or GTmetrix.

The faster your users can interact with the page, the better, isn't it?

Fake loading and lazy imports

The Lite Youtube Embed project by Paul Irish is a perfect example of fake loading: it takes a Youtube Video ID and presents only a thumbnail with a play button :

Lite Youtube Embed Demo

When the play button is clicked, this is when the actual Youtube player is being loaded in order to play the video. By doing that, the page load time is drastically decreased.

Here is the resulting page load with Lite Youtube Embed :

Page load without Lite Youtube Embed

And without :

Page load with Lite Youtube Embed

Live demo

Complex dialogs/modals are also a good use case for this. Initially, there is just a button displayed somewhere on the page. When the user clicks on this button, a modal would show up and allow him to perform various operations, quite often including the use of third party libraries or complex business logic. This is where the import-on-interaction pattern is a really good fit because you won't slow down the page load with code that the user might not even put to use. This is how it is done in Google Docs for the "Share" dialog :

Screenshot 2020-12-16 at 20.14.25

When the button is clicked, the dialog component is loaded, saving 500KB of script for the share feature by deferring its load until user-interaction.

How does this work in React?

It's actually quite surprising how simple it is. When I found out, I immediately wanted to go back on my previous projects and implement it everywhere 😅

Here is a really basic example: let's say you would like to use the react-scroll library for a nicely animated "scroll to the top" feature, triggered when a button is clicked. Here is what your component would look like without the import-on-interaction pattern :

import { animateScroll as scroll } from "react-scroll";

const ScrollToTopBtn = () => {
  const handleClick = () => {
    scroll.scrollToTop();
  };

  return (
    <button onClick={handleClick}>
      Scroll to the top !
    </button>
  );
};
Enter fullscreen mode Exit fullscreen mode

And with lazy loading :

const LazyScrollToTopBtn = () => {
  const handleClick = () => {
    import("react-scroll").then(scroll => {
      scroll.animateScroll.scrollToTop();
    });
  };

  return (
    <button onClick={handleClick}>
      Scroll to the top !
    </button>
  );
};
Enter fullscreen mode Exit fullscreen mode

That's it ! Pretty cool, right ? Also, you can use object destructuring to import animateScroll directly :

const handleClick = () => {
  import("react-scroll").then(({animateScroll}) => {
    animateScroll.scrollToTop();
  });
};
Enter fullscreen mode Exit fullscreen mode

React.lazy and Suspense

React comes with a built-in way of "code-splitting" your app, in order to reduce the size of your bundle. But first, what's code-splitting ? According to React official documentation :

Code-splitting your app can help you “lazy-load” just the things that are currently needed by the user, which can dramatically improve the performance of your app. While you haven’t reduced the overall amount of code in your app, you’ve avoided loading code that the user may never need and reduced the amount of code needed during the initial load.

With the React.lazy function and the Suspense Component, you can render a dynamic import as a regular component :

import React, { Suspense } from 'react';

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here, OtherComponent will only be loaded when MyComponent is first rendered. Suspense allows you to manage the loading state between the render of MyComponent and the moment OtherComponent will be available. This will result in OtherComponent being in a separate chunk of JavaScript. In a way, it's like showing a component in a loading state, fetch data from an API, and then show the component with the data. Only here it's not data you're "fetching", it's your own components 😇

Note: React.lazy and Suspense are not yet available for server-side rendering. If you want to do code-splitting in a server rendered app, React team recommends Loadable Components.


I hope you guys enjoyed this post. This is my first contribution here, as I recently decided to challenge myself to start blogging. English isn't my main language (pardon my French...) and I don't claim to be an absolute React legend, so feel free to let me know if you disagree with anything here, or if you want to discuss some points further. As some wise man once said, explaining is the best way of learning and improving !

Top comments (1)

Collapse
 
carbonid1 profile image
Andrii Korin

Good start! Thanks for the post.