Building a Lazy Loader from Scratch in React (Part 1)
Tags: react, webdev, frontend, javascript
Published: 2023-10-11 09:34:09

## Table of Contents<a name=""></a>

- [Introduction](#Introduction)
- [Prerequisites](#Prerequisites)
    - [Knowledge of React](#Knowledge_of_React)
    - [Knowledge of the `IntersectionObserver` API](#Knowledge_of_the_IntersectionObserver_API)
- [The basic idea](#The_basic_idea)
- [The implementation](#The_implementation)
- [A working lazy loader](#A_working_lazy_loader)


-----

## Introduction<a name="Introduction"></a><a name="Introduction"></a>

In today's complex world of the world wide web, performance remains one of the most important factors for an online business's success and ranking.

While there are many ways to improve the performance of a website, many of them are really easy to implement. **_Lazy loading_** is one such way.

As the name suggests, lazy loading is when resources are loaded **_lazily_**, i.e. only when there is a need to load them. Customarily, these resources are images and videos, and the 'need' to load them arises when they are scrolled into the viewport.

With the advent of the `loading` attribute for images and videos into HTML, it's only a matter of setting it to `"true"` in order to enable lazy loading for a given resource. However, [`loading` is not very well-supported](https://caniuse.com/loading-lazy-attr) across some not-so-old browsers. So, we turn ourselves to creating a lazy loader **from scratch**.

Creating a lazy loader is merely a matter of some [JavaScript](https://www.codeguage.com/courses/js/). And thanks to the [`IntersectionObserver` API](https://www.codeguage.com/courses/advanced-js/intersection-observer-introduction), it has never been this easier to implement lazy loading on the web. _Believe it!_

In this series, we shall learn how to create a lazy loader from scratch using the `IntersectionObserver` API, in [React](https://www.codeguage.com/courses/react/).

_Yes, you heard it right — React._

We've already crafted a comprehensive tutorial on [lazy loading in vanilla JavaScript](https://www.codeguage.com/tutorials/lazy-loading/) on our website, so there's very little point in exploring all that again here.

What we want to do differently now is to borrow from most of those ideas and build a similar lazy loader, albeit in React. React is undoubtedly one of the most popular frontend tools in use all across the globe, and it would be pretty amazing to build a useful functionality using such a useful tool.

_Pretty fascinating, isn't it?_

-----

## Prerequisites<a name="Prerequisites"></a><a name="Prerequisites"></a>

Before we begin developing our lazy loader, let's spare a few minutes for quickly going through the prerequisites that you'll be needing for it.

### 1. Knowledge of React<a name="Knowledge_of_React"></a><a name="Knowledge_of_React"></a>

Starting with the very first thing, since we'll be using React, you need to be well aware of this JavaScript UI library and how it works.

If you're comfortable with such things as [components](https://www.codeguage.com/courses/react/components), [props](https://www.codeguage.com/courses/react/basics#What_are_props), mounting/unmounting, hooks like [`useState()`](https://www.codeguage.com/courses/react/state) and [`useEffect()`](https://www.codeguage.com/courses/react/effects), then you're all set to continue with the discussion below.

Otherwise, if you lack somewhat in these concepts, don't worry, for we have a [completely free course on React](https://www.codeguage.com/courses/react/) to help you improve upon your weak areas.

### 2. Knowledge of the `IntersectionObserver` API<a name="Knowledge_of_the_IntersectionObserver_API"></a><a name="Knowledge_of_the_IntersectionObserver_API"></a>

Moving on, for the sake of brevity, using a modern API, and giving efficiency a first preference, we'll be leveraging the `IntersectionObserver` API for our lazy loader to track when given resources enter the viewport.

The [browser compatibility of `IntersectionObserver`](https://caniuse.com/intersectionobserver) is decent across all modern browsers today without any notable discrepancies between them, which renders the API as a really good choice to go with.

**💡 Notice**: Unlike the `loading` attribute in HTML, the `IntersectionObserver` API can be polyfilled (and that using `scroll` events) and there do exist polyfills.

Henceforth, you'll be required to know how exactly `IntersectionObserver` works because we won't be explaining that in this article.

Once again, if you don't know about the `IntersectionObserver` API, don't worry; we have a unit dedicated to discovering this API in our [Advanced JavaScript course](https://www.codeguage.com/courses/advanced-js/).

Here are the chapters to turn to if you wish to learn about intersection observers:

- [Intersection Observers — Basics](https://www.codeguage.com/courses/advanced-js/intersection-observer-basics): covers what are intersection observers and how they work.
- [Intersection Observers — Entries](https://www.codeguage.com/courses/advanced-js/intersection-observer-entries): explore the `IntersectionObserverEntry` interface and how it works.


And these are pretty much the main prerequisites required, at least for now.

In the subsequent parts of this series, when we'll be adding more features to our lazy loader, we might consider some other prerequisites there which would make more sense to be explored along with those concepts.

Without further ado, let's get into the business.

-----

## The basic idea<a name="The_basic_idea"></a><a name="The_basic_idea"></a>

Getting straightaway into the coding of something without giving any thought into the design of that thing is exciting but not typically rewarding.

So prior to coding our lazy loader, we ideally want to understand how it'll work, what things it'll need, how those things will be integrated together, and so on.

First things first, we'll need a [component](https://www.codeguage.com/courses/react/components) to denote a lazy resource. For now, let's just stick to lazy images, but the ideas can be applied to videos, iframes, and other embeds as well. We'll call it **`LazyImage`**.

Trivially, `LazyImage` will render a descendant `<img>` element, because at the end of the day, we need to show an image to the user and that's the very purpose of `<img>`.

Getting into `<img>`, to be able to defer its loading in HTML, we obviously need to omit its `src` attribute (or point it to an image that's very small in size, probably showcasing a low-quality blur version of the original image).

So we can create a **`src`** prop for `LazyImage` but not relay it forward to the `<img>` until we are sure that we want to initiate the loading of the underlying image.

Let's now talk about the integration of `IntersectionObserver` with our lazy loader.

When a `LazyImage` gets rendered, it'll be observed by an `IntersectionObserver` instance. This `IntersectionObserver` instance will be created once in the global scope and reused across all `LazyImage`s.

The `IntersectionObserver` for our lazy loader will have its<a name="IntersectionObserver_config"></a>

- `root` option set to `null` (which is just the default), as we need to observe elements entering the viewport;
- `threshold` set to `[0]`, in order to fire the observer callback when the image is just touching the viewport (or is beyond that point).


When the callback function of the observer fires for a lazy image and the conditions for loading the image are met (which we'll see below), we'll remove the image from being observed by the `IntersectionObserver` instance.

This isn't required per se, but it's considered a good practice to keep from unnecessarily wasting resources (the observer will be holding on to non-existent targets).

Moreover, if a `LazyImage` gets torn down, for e.g. by virtue of a `LazyImage` component instance being replaced or deleted in the component tree, even then we'll be unobserving the image to, again, preserve computing resources.

And that's the basic idea of implementing a lazy image loader in React.

With this rock-solid plan in hand, now is the time to get coding. _Finally!_

-----

## The implementation<a name="The_implementation"></a><a name="The_implementation"></a>

We'll start by defining the `LazyImage` component.

Recall that it ought to have a `src` prop, containing a value to be ultimately assigned to the `src` attribute of the contained `<img>` element.

With this simple idea in mind, here's the initial definition of `LazyImage`:

```jsx
function LazyImage({ src }) {
   return (
      <div className="lazy">
         <img/>
      </div>
   );
}
```

Notice the `<div>` container that we've used here around `<img>`. Precisely speaking, we don't need it for our lazy loader. But we do need it for a _good_ lazy loader.

Let's find out why.


> #### Why have a `<div>` encapsulating the `<img>`?<a name=""></a>
> 
> Well, firstly, it's a good practice to encapsulate inline elements, such as `<img>`, with block elements, such as `<div>`, if those inline elements have to be shown as separate blocks, as is the case with our lazy images.
> 
> Secondly, in the latter part of this series, when we'll be adding fade effects and fixing CLS issues in loading images naively, we'll be needing a container anyways.
> 
> And so now is the right time to have it.

The implementation shown above does give us a good start for `LazyImage` but it obviously ain't complete right now.

That's what we discuss and do below.

We'll apply the `src` prop to `<img>` only when the underlying image is meant to be initiated for a load. This hints us that we'll need a state value for `LazyImage` to indicate whether its `<img>` has a `src` or not. Let's call it **`srcExists`**.

**💡 Notice**: Setting `src` on the `<img>` element, to begin with, doesn't make sense since it'll start the image's loading whereas we want to defer it until the image shows up.

As per our choice for the name, `srcExists` being `false` would mean that the `LazyImage`'s contained `<img>` doesn't have `src` set.

Here's the extension of the previous code with an inclusion of `srcExists`:

```jsx
import { useState } from 'react';

function LazyImage({ src }) {
   const [srcExists, setSrcExists] = useState(false);

   return (
      <div className="lazy">
         <img src={srcExists ? src : null}/>
      </div>
   );
}
```

The [conditional expression](https://www.codeguage.com/courses/js/conditions-ternary-operator) assigned to `src` above is used so that when `srcExists` is `false`, the value of the `src` prop becomes `null`, which effectively prevents the `src` attribute from being set on the underlying `<img>` element.

You might ask at this stage: _who will call `setSrcExists` and set it to `true`?_ Well, this is the job of the `IntersectionObserver` API, as we shall see up next.

Before anything, let's instantiate an `IntersectionObserver` instance and store it on the `LazyImage` function, under the property **`io`**:

```jsx
function LazyImage({ src }) { /* ... */ }

function ioCallback() {}

LazyImage.io = new IntersectionObserver(ioCallback, {
   root: null,
   threshold: [0]
});
```

We could've just stored this instance in a global variable `io` as well, but we feel that it's better to store it in `LazyImage` since the instance is only meant for `LazyImage`.

The callback function `ioCallback()` will be defined later on below; as for the second _`options`_ argument of the constructor, we've already discussed the `root` and `threshold` properties [above](#IntersectionObserver_config) and so we won't be going into them again here.

With the observer instance created, the next step is to observe a `LazyImage` as soon as it's rendered. If you're experienced in React hooks, you'll immediately say: _"This is the job of `useEffect()`."_

_But wait...it's not that simple._

The `IntersectionObserver` interface is meant to observe **DOM elements**, likewise, if we wish to observe a lazy image, we have to extract out a reference to a DOM element representing the image.

In our case, the element could be either of `<div>` or `<img>`. We'll just go with `<div>`.

Needless to say, since we're dealing with DOM element references in React here, what we ought to use is the `useRef()` hook along with the `ref` prop on the `<div>` element.

**Further reading:**
To learn more about refs in React, consider going through [React Refs](https://www.codeguage.com/courses/react/refs).

Here's the new code we get:

```jsx
import { useState, useEffect } from 'react';

function LazyImage({ src }) {
   const [srcExists, setSrcExists] = useState(false);
   const divRef = useRef();

   useEffect(() => {
      LazyImage.io.observe(divRef.current);
   }, []);

   return (
      <div ref={divRef} className="lazy">
         <img src={srcExists ? src : null}/>
      </div>
   );
}
```

`divRef.current` holds a reference to the `<div>` DOM element, which is passed to the `observe()` method of our `IntersectionObserver` instance inside `useEffect()`'s callback.

_Pretty elementary._

Take note of the dependency list (the second argument) provided to `useEffect()` above — it's an empty array which means that `useEffect()`'s callback would only be invoked once for a given `LazyImage` component instance (which is when the instance gets rendered for the first time).

This is simply because we want to call the `observe()` method only once on a lazy image.

_So far, so good._

Now, let's move over to the intersection observer's callback function,**`ioCallback()`**, where the real action happens.

The idea is as follows: when the callback fires for a given image target, we check if it fired because the image is showing in (or touching with) the viewport. If this check yields success, we set the `src` attribute on the underlying `<img>`, and that by changing `srcExists` to `true`.

But since the `<img>` element is controlled by the React component `LazyImage`, in order to be able to do so, we need access to the state mutating function `setSrcExists()` outside of `LazyImage`.

_So how to do this?_

Fortunately, it's really simple — just assign the `setSrcExists()` function to the `<div>` DOM element that we observe for a `LazyImage`.

Consider the following addition to our `LazyImage` component:

```jsx
import { useState, useEffect } from 'react';

function LazyImage({ src }) {
   const [srcExists, setSrcExists] = useState(false);
   const divRef = useRef();

   useEffect(() => {
      divRef.current.setSrcExists = setSrcExists;
      LazyImage.io.observe(divRef.current);
   }, []);

   return (
      <div ref={divRef} className="lazy">
         <img src={srcExists ? src : null}/>
      </div>
   );
}
```

And here's our `ioCallback()` function:

```jsx
function ioCallback(entries, io) {
   entries.forEach(entry => {
      if (entry.intersectionRatio >= 0 && entry.isIntersecting) {
         io.unobserve(entry.target);
         entry.target.setSrcExists(true);
      }
   });
}
```

And this should pretty much do it for our minimal lazy loader.

Without any doubt whatsoever, this ain't the best lazy loader, but that's okay for now since we've just begun the development; the refinements are to come very soon.

For now, the only additional thing we'll do, to be able to better visualize our lazy loader in action, is to give some minimum height to the `.lazy` element along with a background color so that we can see it prior to the image's display.

Here's the rudimentary CSS:

```css
.lazy {
   min-height: 100px;
   background-color: #ddd
}
```

In addition to this, we'll also set up a 1s timeout inside `ioCallback()` to defer the beginning of the loading of the image (for 1 second). This, again, is to be able to better visualize the lazy loading functionality.

Here's the rewritten `ioCallback()` function:

```jsx
function ioCallback(entries, io) {
   entries.forEach(entry => {
      if (entry.intersectionRatio >= 0 && entry.isIntersecting) {
         setTimeout(() => {
            io.unobserve(entry.target);
            entry.target.setSrcExists(true);
         }, 1000);
      }
   });
}
```

And with this, we're done with our lazy loader's implementation.

Now, it's time to see it in real action.

-----

## A working lazy loader<a name="A_working_lazy_loader"></a><a name="A_working_lazy_loader"></a>

Before we begin this section, note that we're assuming that we have an application set up as shown in our article [How to Set Up Rollup to Run React?](https://www.codeguage.com/blog/setup-rollup-for-react), using the bundler [Rollup](https://rollupjs.org/).

The complete code of this example, including all the setup files, can be found on our GitHub page, at [lazy-loader-react-1](https://github.com/codeguage-code/blog/tree/master/lazy-loader-react-1).


> #### Our React setup<a name=""></a>
> 
> To summarize our application setup, which is quite a familiar one, we have two directories in our demo project:
> 
> - A `public` directory that acts as the root directory for `http://localhost:3000`.
> - A `src` directory that's just meant to contain all the JavaScript/JSX source files. These files are eventually bundled together and exported as a single JavaScript file in `public`.
> 
> 
> Inside `src`, we have an `index.js` that loads another file called `App.jsx` and renders its default-exported `App` component inside a `#root` DOM element in the `index.html` file (in `public`).
> 
> If you've worked with React using a custom build process before, all this setup wouldn't be any new to you.

For this demonstration, let's just go with one `<LazyImage>` instance, and that placed at a very large distance from the top of the document.

The image that we'll be using is the following one, [by Lukas from Pexels](https://www.pexels.com/photo/blue-retractable-pen-574070/):


![Photo by Lukas from Pexels](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ju5995ppsiwmltg68pwm.png)



You can obviously choose any image that you want to.

We've saved the image under the name `image.jpg` in the directory where our `index.html` file resides.

Here's our `App.jsx` file:

```jsx
import LazyImage from './LazyImage';

function App() {
   return (
      <>
         <h1>Demonstrating lazy loading</h1>
         <p style={{ marginBottom: 1000 }}>Slowly scroll down until you bring the lazy image into view.</p>
         <LazyImage src="/image.jpg" />
      </>
   );
}

export default App;
```

The `marginBottom` style applied to the `<p>` element makes sure that the lazy image is initially out of view in the viewport.

Here's a live example:

[Live Example](https://www.codeguage.com/courses/react/examples/blog-lazy-loader-1)

Scroll down the page and just wait for a second before the image gets shown to you. This is lazy loading in action.

As we said before, and as you can even realize by looking at the live example above, this lazy loader isn't done yet. There's a lot to add to it and that's what we'll be doing throughout the subsequent parts of this series.

For now, let's take a break 🍵, for part 2 is to be continued...

-----

## More from CodeGuage

In the meanwhile, you can consider going through some of the following articles published by us on Dev 👇:

{% embed https://dev.to/codeguage/150-questions-to-practice-javascripts-foundational-concepts-1ong %}

{% embed https://dev.to/codeguage/is-frontend-development-easier-than-backend-h77 %}

{% embed https://dev.to/codeguage/how-to-set-up-rollup-for-a-react-application-2322 %}

{% embed https://dev.to/codeguage/4-incredible-vs-code-features-that-you-cant-miss-1nf4 %}