Introduction
We all have heard of lazy loading images, and some of you might have used them too.
It is as easy as adding a loading attribute to your image tag. Like below example
<img src="https://image-source.png" alt="image" loading="lazy" />
But, Adding just a loading attribute to your image in React won't work because of client-side rendering.
So how to lazy load images in ReactJS?
Let's see.
Normal Use Case of Images In ReactJS
import { useEffect, useState } from "react";
function App() {
const [photos, setPhotos] = useState(null);
useEffect(() => {
fetch("https://picsum.photos/v2/list")
.then((res) => res.json())
.then((data) => setPhotos(data));
}, []);
return (
<div className="App">
<div className="imageContainer">
{photos &&
photos.map((photo) => (
<img
key={photo.id}
src={photo.download_url}
alt="dummy-img"
style={{ width: "100%" }}
/>
))}
</div>
</div>
);
}
export default App;
Above, I made a simple fetch request, mapped through image data, and rendered it into the browser.
After opening the network tab, you can see the huge amount of data consumed for the images we have yet to see in the viewport.
Solution:
We will use Intersection Observer API provided by the web browsers to monitor which images are in the viewport and which are not. This way, we will only request the images in the viewport 😀.
Let's start by creating a new component called LazyImage.js that will take props and handle all the logic.
Custom Lazy Loading Image Component
LazyImage.js
import { useRef, useState } from "react";
const LazyImage = ({
placeholderSrc,
placeholderClassName,
placeholderStyle,
src,
alt,
className,
style,
}) => {
const [isLoading, setIsLoading] = useState(true);
const placeholderRef = useRef(null);
return (
<>
{isLoading && (
<img
src={placeholderSrc}
alt=""
className={placeholderClassName}
style={placeholderStyle}
ref={placeholderRef}
/>
)}
<img
src={src}
className={className}
style={isLoading ? {display: "none"} : style}
alt={alt}
onLoad={() => setIsLoading(false)}
/>
</>
);
};
export default LazyImage;
Above, we have created a placeholder image that will immediately mount as the isLoading state is true. Then we applied an onLoad function to the actual image that will set the isLoading state to false so the placeholder image will get unmounted. When the isLoading state is true, we have to hide the actual image from showing, So we conditionally styled it by giving it a property of display: "none".
We have created a safe placeholder image to show until the image is completely loaded, which is good, as you can see in the above image.
But, still, all the images are being requested, which we don't want. We only want to request the images that are in the viewport. So now it's time to implement Intersection Observer 😀.
Final Code for Lazy Loading Image Component
LazyImage.js
import { useEffect, useRef, useState } from "react";
const LazyImage = ({
placeholderSrc,
placeholderClassName,
placeholderStyle,
src,
alt,
className,
style,
}) => {
const [isLoading, setIsLoading] = useState(true);
const [view, setView] = useState("");
const placeholderRef = useRef(null);
useEffect(() => {
// Initiating Intersection Observer
const observer = new IntersectionObserver((entries) => {
// Set actual image source && unobserve when intersecting
if (entries[0].isIntersecting) {
setView(src);
observer.unobserve(placeholderRef.current);
}
});
// observe for an placeholder image
if (placeholderRef && placeholderRef.current) {
observer.observe(placeholderRef.current);
}
}, [src]);
return (
<>
{isLoading && (
<img
src={placeholderSrc}
alt=""
className={placeholderClassName}
style={placeholderStyle}
ref={placeholderRef}
/>
)}
<img
src={view} // Gets src only when placeholder intersecting
className={className}
style={isLoading ? {display: "none"} : style}
alt={alt}
onLoad={() => setIsLoading(false)}
/>
</>
);
};
export default LazyImage;
As you can see in the above code, we have initiated the intersection observer inside the useEffect hook, so it will apply only once the component is mounted.
Then we observe the placeholder image to check if it is intersecting (do note that on mount placeholder image will only occupy the space in the DOM.), and once the placeholder is intersecting, we will pass the src we accepted as a prop to the view state we created.
In this way, the actual image will start to make requests 🤩
Ultimately, we will unobserve the placeholder image to stop listening for the changes.
Boom! 🔥
You can see we are only making requests for the images in the viewport.
I have created a package for you to use, so you don't have to make it yourself 😛 Visit HERE
Or run this npm command in your terminal.
npm install @kunalukey/react-image
Video Tutorial:
Thanks for reading! ❤
Top comments (2)
Hi,which resource do you recommend to learn mern stack ?
I would recommend you to try: roadmap.sh
and some youtube channels like Traversy Media, The Net Ninja, and FreeCodeCamp.