How to write a progressive image loading hook
Tags: react, javascript, frontend, images
Published: 2019-05-09 08:11:07

---
title: How to write a progressive image loading hook
published: true
description: Want to display a fallback thumbnail while you download your hero image? Then this article is for you!
tags: reactjs, javascript, frontend, images
cover_image: https://images.unsplash.com/photo-1518655061710-5ccf392c275a?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1400&q=80
canonical_url: https://www.selbekk.io/blog/2019/05/how-to-write-a-progressive-image-loading-hook/
---

While we tune every kilobyte out of our JavaScript bundles, we often forget to optimize our image loading strategies the same way. We might be sitting looking at a blank screen for several seconds before the hero image loads, giving the background to your white text.

This article is going to show you how you can write a hook that handles your progressive image loading for you!

> ## What's progressive image loading?
> Progressive image loading - at least in this context - is loading a very low-resolution version of the image first, while loading the high resolution version in the background. Once the high resolution version is loaded, the images are swapped.

We're going to name our hook `useProgressiveImage`, and pass it an object of a `src` prop and a `fallbackSrc` prop. It will return the best available image src already loaded, or `null` if neither has loaded yet.

```js
function useProgressiveImage({ src, fallbackSrc }) {
  return null;
}
```

We can pre-load images like this by creating a new `Image` instance, and setting its `src` attribute. We can listen to its `onload` event, and react to it accordingly. Let's write out some of this boilerplate code:

```js
function useProgressiveImage({ src, fallbackSrc }) {
  const mainImage = new Image();
  const fallbackImage = new Image();

  mainImage.onload = () => {}; // Still todo
  fallbackImage.onload = () => {}; // Still todo

  mainImage.src = src;
  fallbackImage.src = fallbackSrc;

  return null;
}
```

This is going to run on every render though - which is going to trigger a ton of useless network requests. Instead, let's put it inside a `useEffect`, and only run it when the `src` or `fallbackSrc` props change.

```js
function useProgressiveImage({ src, fallbackSrc }) {
  React.useEffect(() => {
    const mainImage = new Image();
    const fallbackImage = new Image();

    mainImage.onload = () => {}; // Still todo
    fallbackImage.onload = () => {}; // Still todo

    mainImage.src = src;
    fallbackImage.src = fallbackSrc;
  }, [src, fallbackSrc]);

  return null;
}
```

Next, we need to keep track of which image has been loaded. We don't want our fallback image to "override" our main image if that would load first (due to caching or just coincidence), so we need to make sure to implement that. 

I'm going to keep track of this state with the [`React.useReducer`](https://reactjs.org/docs/hooks-reference.html#usereducer) hook, which accepts a _reducer function_. This reducer function accepts the previous state (loaded source), and returns the new state depending on what kind of action we dispatched.

```js
function reducer(currentSrc, action) {
  if (action.type === 'main image loaded') {
    return action.src;
  } 
  if (!currentSrc) {
    return action.src;
  }
  return currentSrc;
}

function useProgressiveImage({ src, fallbackSrc }) {
  const [currentSrc, dispatch] = React.useReducer(reducer, null);
  React.useEffect(() => {
    const mainImage = new Image();
    const fallbackImage = new Image();

    mainImage.onload = () => {
      dispatch({ type: 'main image loaded', src });
    };
    fallbackImage.onload = () => {
      dispatch({ type: 'fallback image loaded', src: fallbackSrc });
    };

    mainImage.src = src;
    fallbackImage.src = fallbackSrc;
  }, [src, fallbackSrc]);

  return currentSrc;
}
```

We've implemented two types of actions here - when the main image is loaded and when the fallback image is loaded. We leave the business logic to our reducer, which decides when to update the source and when to leave it be.

> ### What's with the action types?
> If you're like me, you're used to reading action types in `CONSTANT_CASE` or at the very least `camelCase`. Turns out, however, you can call them exactly what you want. I was feeling playful here, and just wrote out the intent. Because why not? 😅Since they're only internal to this little hook anyways, it truly doesn't matter much anyhow.

Using our hook is pretty straight forward too.

```js
const HeroImage = props => {
  const src = useProgressiveImage({ 
    src: props.src,
    fallbackSrc: props.fallbackSrc 
  });
  if (!src) return null;
  return <img className="hero" alt={props.alt} src={src} />;
};
```

I've created a CodeSandbox you can check out and play with if you want!

{% codesandbox pkpqn1vo1x %}

Thanks for reading my little mini-article! I always appreciate a share or like or comment, to let me know whether I should keep these coming or not. 

Until next time!