React lazy image loading and TypeScript — No more slow and broken images
Creating a better UX is not as simple as it looks. Every component on-page matters. While working on a complex piece of code, we almost forgot about the simplest thing, a broken image.
Here in this tutorial, I will explain how you can create a simple Image component without breaking existing code. This component will support lazy loading and broken images.
Photo by ©Joanna Kosinska
1. Create an Image Component
To start with, First Create a folder in your component library and Create a file named index.tsx
.
mkdir -p components/Image
touch components/Image/index.tsx
Add below line to index.tsx
.
// components/Image/index.tsx
import React from "react";
interface ImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {}
export default ({ ...props }: ImageProps) => {
return <img {...props} />;
};
Note: I am using typescript to build this component. You can just remove typing if your project does not support typescript.
2. How to use Image in App Page
You can consume the Image component just like img
HTML element. Since this image component extends HTMLImageElement
. It supports all the Image properties.
// App.tsx
import React from "react";
import Image from "./components/Image";
function App() {
return (
<div className="App">
<section className="section">
<div className="container">
<h1 className="title">React Components Demo</h1>
<p className="subtitle">Lazy Image</p>
<Image
style={{ width: 400, height: 300 }}
src="https://upload.wikimedia.org/wikipedia/commons/f/ff/Pizigani_1367_Chart_10MB.jpg"
/>
<br />
<Image src="https://doesnot.exits.com/image.png" />
</div>
</section>
</div>
);
}
export default App;
Try to reload the page, you will notice the image load very slowly. This is because the image is 10mb
large. Same time the other image will be broken.
3. Add a default image prop(placeholder)
Let's add a default image prop, modify the interface.
// components/Image/index.tsx
interface ImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
placeholderImg?: string;
}
Take placeholderImg
image and show this to the user before the actual image loaded.
// components/Image/index.tsx
export default ({ placeholderImg, src, ...props }: ImageProps) => {
const [imgSrc, setSrc] = useState(placeholderImg || src);
return <img {...props} src={imgSrc} />;
};
No question will arise if we are showing a placeholder image, the how we will load the actual image.
4. Load Image Dynamically
For that, we will use the Image class
from JavaScript. We will create an image dynamically and add an event listener to it.
// components/Image/index.tsx
export default ({ src, placeholderImg, ...props }: ImageProps) => {
const [imgSrc, setSrc] = useState(placeholderImg || src);
const onLoad = useCallback(() => {
setSrc(src);
}, [src]);
useEffect(() => {
const img = new Image();
img.src = src as string;
img.addEventListener("load", onLoad);
return () => {
img.removeEventListener("load", onLoad);
};
}, [src, onLoad]);
return <img {...props} alt={imgSrc} src={imgSrc} />;
};
Here we are using useEffect
to create the image and load it in the background. We can listen to the load event using addEventListener
. Once the image is loaded we can replace src with the actual src URL. Since the image has already loaded and cached to the browser. The next loading will be in no time.
Since we are adding event listeners to the image on load and error on mount of the component. We should remove on unmount of the component.
Let’s modify the App code.
// App.tsx
<Image
style={{width: 400, height: 300 }}
placeholderImg="https://via.placeholder.com/400x200.png?text=This+Will+Be+Shown+Before+Load"
src="https://upload.wikimedia.org/wikipedia/commons/f/ff/Pizigani_1367_Chart_10MB.jpg"
/>
<br />
<Image
placeholderImg="https://via.placeholder.com/400x200.png?text=This+Will+Be+Shown+Before+Load"
src="https://doesnot.exits.com/image.png"
/>
If you notice, the second image is still showing the placeholder image. Since the image never loaded successfully so setSrc
never been called.
5. Handle Error State
Let’s handle the image loading error. For that let’s add another prop errorImg and listen on error status.
// components/Image/index.tsx
import React, { useCallback, useEffect, useState } from "react";
interface ImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
placeholderImg?: string;
errorImg?: string;
}
export default ({ src, placeholderImg, errorImg, ...props }: ImageProps) => {
const [imgSrc, setSrc] = useState(placeholderImg || src);
const onLoad = useCallback(() => {
setSrc(src);
}, [src]);
const onError = useCallback(() => {
setSrc(errorImg || placeholderImg);
}, [errorImg, placeholderImg]);
useEffect(() => {
const img = new Image();
img.src = src as string;
img.addEventListener("load", onLoad);
img.addEventListener("error", onError);
return () => {
img.removeEventListener("load", onLoad);
img.removeEventListener("error", onError);
};
}, [src, onLoad, onError]);
return <img {...props} alt={imgSrc} src={imgSrc} />;
};
Let’s modify the App code.
// App.tsx
import React from "react";
import Image from "./components/Image";
function App() {
return (
<div className="App">
<section className="section">
<div className="container">
<h1 className="title">React Components Demo</h1>
<p className="subtitle">Lazy Image</p>
<Image
style={{ width: 400, height: 300 }}
placeholderImg="https://via.placeholder.com/400x200.png?text=This+Will+Be+Shown+Before+Load"
src="https://upload.wikimedia.org/wikipedia/commons/f/ff/Pizigani_1367_Chart_10MB.jpg"
/>
<br />
<Image
errorImg="https://image.shutterstock.com/image-vector/no-image-available-vector-illustration-260nw-744886198.jpg"
placeholderImg="https://via.placeholder.com/400x200.png?text=This+Will+Be+Shown+Before+Load"
src="https://doesnot.exits.com/image.png"
/>
</div>
</section>
</div>
);
}
export default App;
Conclusion: Now we have achieved broken image and lazy loading image both with the same component.
Note: You can use the placeholder image as a loader and show the loading image before loading the actual image. For that, you can use transition animation or Gif.
CodeSandbox Domo:
Thanks for your support. You can find all code in GitHub Repo: deepakshrma/react-components