How to Lazy Load Images in React Using Intersection Observer API

In this blog post we are going to take a look at the Intersection Observer API and how we can use it to lazy load images and we will by using it in a React project. This can be useful when you have a grid of images and you want to load them when user scrolls down or up the page. This can save bandwidth and you only load what you need. This can also be used to play videos or load ads when appropriate.

For the demo I am I am replacing a placeholder tile with the actual tile but you can easily extend this to load image from a server. To get started I have a grid with tiles that simulate images.

I have created a hook called use-visibility-detector to tap into the Intersection Observer API. An Intersection Observer takes in a callback function and some options. It looks like this:

IntersectionObserver(callback, options);

The callback is called every time the threshold is crossed in one way or the other. I have defined the options like this:

let options = {
  rootMargin: "10px",
  threshold: 0.9
};

0.9 means when 90% of the object is visible but if you want to trigger the callback when 10% of the target is visible then change it to 0.1. The callback is defined like this:

  let observerCallback = (entries, observer) => {
    entries.forEach((entry) => {
      const listener = SUBSCRIBERS.get(entry.target);
      if (listener) {
        listener(entry.intersectionRatio > 0);
      }
    });
  };

We create a map of subscribers that we can add elements to. You can see that in action inside the useEffect hook. Here is the sample:

const SUBSCRIBERS = new Map();
...
  useEffect(() => {
    ...code...
    SUBSCRIBERS.set(element, (visible) => {
      ...code...
    });

    // cleanup
  }, [...deps...]);

When a subscriber is added to the map we add a listener that will be called when this element passes the threshold. This listener is called by the observer callback that we defined above when creating Intersection Observer object.

We use the useRef hook to get a reference to the target which is passed by the target in the visibilityRef prop. There is also the isVisible prop that is used by the client to swap the placeholder image with actual tile. You can take a look at the source code here:

https://codesandbox.io/embed/github/grepsoft/react-intersection-observer/tree/main/?fontsize=14&hidenavigation=1&theme=dark

You can also find it on github.