Skip to content

React: How do you lazyload image from API response?

My website is too heavy because it downloads 200-400 images after fetching data from the server (Google’s Firebase Firestore).

I came up with two solutions and I hope somebody answers one of them:

  • I want to set each img to have a loading state and enable visitors to see the placeholder image until it is loaded. As I don’t know how many images I get until fetching data from the server, I find it hard to initialize image loading statuses by useState. Is this possible? Then, how?
  • How can I Lazy load images? Images are initialized with a placeholder. When a scroll comes near an image, the image starts to download replacing the placeholder.
function sample() {}{
  const [items, setItems] = useState([])
  const [imgLoading, setImgLoading] = useState(true)  // imgLoading might have to be boolean[]
  useEffect(() => {
    .then(response => setItems(
  }, [])
  return ( => <img src={item.imageUrl} onLoad={setImgLoading(false)} />)



There are libraries for this, but if you want to roll your own, you can use an IntersectionObserver, something like this:

const { useState, useRef, useEffect } = React;

const LazyImage = (imageProps) => {
  const [shouldLoad, setShouldLoad] = useState(false);
  const placeholderRef = useRef(null);

  useEffect(() => {
    if (!shouldLoad && placeholderRef.current) {
      const observer = new IntersectionObserver(([{ intersectionRatio }]) => {
        if (intersectionRatio > 0) {
      return () => observer.disconnect();
  }, [shouldLoad, placeholderRef]);

  return (shouldLoad 
    ? <img {...imageProps}/> 
    : <div className="img-placeholder" ref={placeholderRef}/>

  <div className="scroll-list">
    <LazyImage src=''/>
    <LazyImage src=''/>
    <LazyImage src=''/>
    <LazyImage src=',45,480,270_AL_UX477_CR0,0,477,268_AL_.jpg'/>
.scroll-list > * {
  margin-top: 400px;

.img-placeholder {
  content: 'Placeholder!';
  width: 400px;
  height: 300px;
  border: 1px solid black;
  background-color: silver;
<div id="app"></div>

<script src=""></script>
<script src=""></script>

This code is having them load as soon as the placeholder is visible on the screen, but if you want a larger detection margin, you can tweak the rootMargin option of the IntersectionObserver so it starts loading while still slightly off screen.

User contributions licensed under: CC BY-SA
4 People found this is helpful