Make Your Own Lazy Loading Image Component In React

make-your-own-lazy-loading-image-component-in-react

Introduction

We all have heard of lazy loading images, and some of you might have used them too.
It is as easy as adding a loading attribute to your image tag. Like below example

 src="https://image-source.png" alt="image" loading="lazy" />

But, Adding just a loading attribute to your image in React won’t work because of client-side rendering.
So how to lazy load images in ReactJS?
Let’s see.

Normal Use Case of Images In ReactJS

import { useEffect, useState } from "react";

function App() {
  const [photos, setPhotos] = useState(null);

  useEffect(() => {
    fetch("https://picsum.photos/v2/list")
      .then((res) => res.json())
      .then((data) => setPhotos(data));
  }, []);

  return (
    <div className="App">
      <div className="imageContainer">
        {photos &&
          photos.map((photo) => (
            <img
              key={photo.id}
              src={photo.download_url}
              alt="dummy-img"
              style={{ width: "100%" }}
            />
          ))}
      </div>
    </div>
  );
}

export default App;

Above, I made a simple fetch request, mapped through image data, and rendered it into the browser.

Non-Lazy-Images After opening the network tab, you can see the huge amount of data consumed for the images we have yet to see in the viewport.

Solution:

We will use Intersection Observer API provided by the web browsers to monitor which images are in the viewport and which are not. This way, we will only request the images in the viewport 😀.

Let’s start by creating a new component called LazyImage.js that will take props and handle all the logic.

Custom Lazy Loading Image Component

LazyImage.js

import { useRef, useState } from "react";

const LazyImage = ({
  placeholderSrc,
  placeholderClassName,
  placeholderStyle,
  src,
  alt,
  className,
  style,
}) => {
  const [isLoading, setIsLoading] = useState(true);
  const placeholderRef = useRef(null);

  return (
    <>
      {isLoading && (
        <img
          src={placeholderSrc}
          alt=""
          className={placeholderClassName}
          style={placeholderStyle}
          ref={placeholderRef}
        />
      )}
      <img
        src={src}
        className={className}
        style={isLoading ? {display: "none"} : style}
        alt={alt}
        onLoad={() => setIsLoading(false)}
      />
    </>
  );
};
export default LazyImage;

Above, we have created a placeholder image that will immediately mount as the isLoading state is true. Then we applied an onLoad function to the actual image that will set the isLoading state to false so the placeholder image will get unmounted. When the isLoading state is true, we have to hide the actual image from showing, So we conditionally styled it by giving it a property of display: “none” when the isLoading state is true.

Placeholder-Images We have created a safe placeholder image to show until the image is completely loaded, which is good, as you can see in the above image.
But, still, all the images are being requested, which we don’t want. We only want to request the images that are in the viewport. So now it’s time to implement Intersection Observer 😀.

Final Code for Lazy Loading Image Component

LazyImage.js

import { useEffect, useRef, useState } from "react";

const LazyImage = ({
  placeholderSrc,
  placeholderClassName,
  placeholderStyle,
  src,
  alt,
  className,
  style,
}) => {
  const [isLoading, setIsLoading] = useState(true);
  const [view, setView] = useState("");
  const placeholderRef = useRef(null);

  useEffect(() => {
    // Initiating Intersection Observer
    const observer = new IntersectionObserver((entries) => {
      // Set actual image source && unobserve when intersecting
      if (entries[0].isIntersecting) {
        setView(src);
        observer.unobserve(placeholderRef.current);
      }
    });

    // observe for an placeholder image
    if (placeholderRef && placeholderRef.current) {
      observer.observe(placeholderRef.current);
    }
  }, [src]);

  return (
    <>
      {isLoading && (
        <img
          src={placeholderSrc}
          alt=""
          className={placeholderClassName}
          style={placeholderStyle}
          ref={placeholderRef}
        />
      )}
      <img
        src={view} // Gets src only when placeholder intersecting
        className={className}
        style={isLoading ? {display: "none"} : style}
        alt={alt}
        onLoad={() => setIsLoading(false)}
      />
    </>
  );
};
export default LazyImage;

As you can see in the above code, we have initiated the intersection observer inside the useEffect hook, so it will apply only once the component is mounted.
Then we observe the placeholder image to check if it is intersecting (do not that on mount placeholder image will only occupy the space in the DOM.), and once the placeholder is intersecting, we will pass the src we accepted as a prop to the view state we created.
In this way, the actual image will start to make requests 🤩
Ultimately, we will unobserve the placeholder image to stop listening for the changes.

Lazy-Images Boom! 🔥
You can see we are only making requests for the images in the viewport.

I have created a package for you to use, so you don’t have to make it yourself 😛 Visit HERE
Or run this npm command in your terminal.

npm install @kunalukey/react-image

Video Tutorial:

Thanks for reading! ❤

Total
1
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post
simulating-multiple-client-load-tests-in-gatling-with-github-actions

Simulating Multiple Client Load Tests in Gatling with GitHub Actions

Next Post
django-htmx-load-more-objects

DJANGO HTMX LOAD MORE OBJECTS

Related Posts