Progressive Images in React
Tags: react, progressive, image, lazy
Published: 2018-08-20 20:25:33

---
title: Progressive Images in React
published: true
description: A short tutorial for rendering lazy-loading, efficient images in React.
tags: react,progressive,image,lazy
---

Almost every article on a digital publication's website will have an image somewhere. Most of these have multiple images. Sometimes 10, 20... 30... 💤 

<img src="https://thepracticaldev.s3.amazonaws.com/i/6cwjucjndxb0k1ihyzk5.jpg" />

#### So be lazy!

Lazily loading images to decrease load time is nothing new. People have come up with different implementations utilizing various concepts, but the goal is the same: load the page as fast as possible while minimally affecting UX.

This walkthrough will (hopefully) give you an understanding of a way to load an image in a performant way, similar to the way [Medium](https://jmperezperez.com/medium-image-progressive-loading-placeholder/) loads images. It assumes basic knowledge of React, and that server-rendered components are being provided with a javascript bundle on the client.

# Render static component on server ❄️

Let's make a basic component that can render two images, which we will switch between later:

```jsx
const BlurredImg = styled.img`
	filter: blur(6px);
	transition: opacity 0.2s ease-in;
`;

const smallImageUrl = `www.images.com/low-quality/${this.props.id}.${this.props.format}`;
const fullImageUrl = `www.images.com/high-quality/${this.props.id}.${this.props.format}/`;

return (
    <React.Fragment>
        <BlurredImg src={smallImageUrl} data-id={this.props.id} data-format={this.props.format} />
        {this.props.pageLoaded && <img src={fullImageUrl} />}
    </React.Fragment>
);
```

_If you aren't familiar with [styled-components](https://github.com/styled-components/styled-components), that `styled` syntax is essentially a block of CSS around an `img` tag._

The `pageLoaded` prop will prevent the higher-quality image from being rendered on the server. We'll set this to `true` when hydrating later.

The `BlurredImg` element is doing two things:
 - 1. Rendering a low-quality version of image, with blur
 - 2. Exposing [`data` attributes](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes) to the DOM*

*Because the server is returning raw HTML, the props we used to render the component will be lost. How you choose to keep props consistent between server and client is up to you; this implementation will rely on data attributes to keep things simple. Alternatively, you could pass a blob of data keyed by image `id`, etc.

# Hydrate component on client 💧

<img src="https://thepracticaldev.s3.amazonaws.com/i/pzru8avly79ho8vkm7rq.gif" />

Hydrating the component allows it to load the higher-quality image to replace the blurred version. As mentioned before, the props are determined by data attributes:

```jsx
const imageElement = document.querySelector('.lazy-image-wrapper');

ReactDOM.hydrate(
	<ProgressiveImageExample
		id={imageElement.dataset.id}
		format={imageElement.dataset.format}
		pageLoaded={true}
	/>,
	imageElement
);
```

Note that `pageLoaded` is now `true`, which enables the real `<img>` to load. But the blurred image still shows even though the other is loaded...

# Swap the images 🌃🤝🏞

This introduces complexity that requires `state`, so we'll need to add it in the constructor:

```js
constructor(props) {
    super(props);

    this.state = {
        imageLoaded: false
    };
}
```

And update our components to be more `state`ful:

```jsx
const BlurredImg = styled.img`
	filter: blur(6px);
	opacity: ${props => props.shouldBeVisible ? 1 : 0};
	transition: opacity 0.2s ease-in;
`;

return (
    <React.Fragment>
        <BlurredImg 
            src={smallImageUrl} 
            data-id={this.props.id} 
            data-format={this.props.format} 
            shouldBeVisible={!this.state.imageLoaded} 
        />
        {this.props.pageLoaded && <img src={fullImageUrl} onLoad={this.handleImageLoaded} />}
    </React.Fragment>
);
```

Note the `onLoad` handler - this signals that the real image is ready & we can fade the blurred image out:

```js
handleImageLoaded = () => {
	this.setState({
		imageLoaded: true
	});
}
```

Great! So we have images loading after initially loading the page. But this only gets us most of the way, since they will all load immediately upon hydration (for all images on the page). So what do we do?

# Track visibility 🔎

<img src="https://thepracticaldev.s3.amazonaws.com/i/d9t558qjq9l6n26bwskk.gif" />

The [react-on-screen](https://github.com/fkhadra/react-on-screen) library makes for a simple higher-order component that does this, passing an `isVisible` prop to the component. But any in-viewport visibility tracker will do. Since this introduces another `state`, we'll need to add `{ imageSeen: false }` to our initial state.

Then, we can watch for updated props and set `imageSeen` to `true` when the image has been in the viewport, preventing it from being loaded again when scrolling the component off-screen:


```js
componentDidUpdate() {
	if (!this.state.imageSeen && this.props.isVisible) {
		this.setState(prevState => ({
			...prevState,
			imageSeen: this.props.isVisible
		}));
	}
}
```

And there you have it! An image rendering component that's quick, efficient, and [not lame](https://www.youtube.com/watch?v=Msj9aLUdFkQ) at all!