Make Your Own Lazy Loading Image Component In React
Tags: react, tutorial, webdev, javascript
Published: 2022-11-21 09:10:22

## 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 

```HTML
<img 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

```javascript
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/caw6sdtwo47w4t0bwlxa.PNG) 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

```javascript
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"**.

![Placeholder-Images](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/48jl9uzo4cxr8t2nko1x.png) 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

```javascript
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 note 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bu3wz0j5alt3ou5lvtfk.PNG) 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](https://www.npmjs.com/package/@kunalukey/react-image)
Or run this npm command in your terminal.
```bash
npm install @kunalukey/react-image
```

Video Tutorial: {% youtube xxVtogqXzgc %}

Thanks for reading! ❤