When I performed a manual deep linking hook in a web application, the automatically scrolling down to a specific section caused a delay by loading of images.
How to detect the loading issues of the images before executing any action in react? The next hook uses eventListener
with load
and error
events, and detects the HTMLImageElement.complete property of javascript, to determine if all images in a specific wrapper element have been completed.
import { useState, useEffect, RefObject } from "react";
export const useOnLoadImages = (ref: RefObject<HTMLElement>) => {
const [status, setStatus] = useState(false);
useEffect(() => {
const updateStatus = (images: HTMLImageElement[]) => {
setStatus(
images.map((image) => image.complete).every((item) => item === true)
);
};
if (!ref?.current) return;
const imagesLoaded = Array.from(ref.current.querySelectorAll("img"));
if (imagesLoaded.length === 0) {
setStatus(true);
return;
}
imagesLoaded.forEach((image) => {
image.addEventListener("load", () => updateStatus(imagesLoaded), {
once: true
});
image.addEventListener("error", () => updateStatus(imagesLoaded), {
once: true
});
});
return;
}, [ref]);
return status;
};
Note: is important to add both load
and error
to avoid any blocking after load page.
According with the documentation of complete
prop, the image is considered completely loaded if any of the following are true:
- Neither the src nor the srcset attribute is specified. The srcset attribute is absent and the src attribute, while specified, is the empty string ("").
- The image resource has been fully fetched and has been queued for rendering/compositing.
- The image element has previously determined that the image is fully available and ready for use.
- The image is "broken;" that is, the image failed to load due to an error or because image loading is disabled.
To use it you have to pass a ref wrapper to limit the search images.
import { useRef } from "react";
import { useOnLoadImages } from "./hooks/useOnLoadImages";
import "./styles.css";
export default function App() {
const wrapperRef = useRef<HTMLDivElement>(null);
const imagesLoaded = useOnLoadImages(wrapperRef);
return (
<div className="App" ref={wrapperRef}>
<h2>How to detect images loaded in React</h2>
<div>
<p>{!imagesLoaded ? "Loading images..." : "Images loaded"}</p>
<img src="https://source.unsplash.com/1600x900/?nature" alt="nature" />
<img src="https://source.unsplash.com/1600x900/?water" alt="water" />
<img src="https://source.unsplash.com/1600x900/?animal" alt="animal" />
<img src="https://source.unsplash.com/1600x900/?lake" alt="lake" />
<img src="https://source.unsplash.com/1600x900/?life" alt="life" />
</div>
</div>
);
}
Here there are a demo Link (reload internal browser)
If you like the article follow me in:
Top comments (4)
Great article, thanks!
I was wondering if there is a specific reason to do a .map before the .every check?
images.map((image) => image.complete).every((item) => item === true)
wouldn't it be more simple to just check for
images.every((item) => item.complete === true)
instead, and save one extra loop?Wouldn't it be easier to do it this way? and in general, you have some strange mechanics of work, when loading each element, you have to perform a million iterations of the cycle
Updated: Plus, after the tests, I want to add that this code will not even work since
useEffect
will not respond toref
, you need to change it toref.current
Note that this will run into a race condition if you set the
src
attribute on your images before you register theirload
listeners. I was able to reproduce this by refreshing the Codesandbox demo iframe; the images load very quickly because they were cached by the browser. Unfortunately, this means that by the time the event listener is registered, the load event will have already fired. So the message is never displayed.See here for more context: stackoverflow.com/questions/146485....
Yes you're right, I had to use it in an app when the content comes from a CMS and we parse it, and normally the images aren't cached. I'm going to review it on this case. Thanks.