Apr 25, 2023April 25th, 2023 · 1 minute read · tweet
useImageIsLoaded
A simple React hook to track the loading state of an image.
A very common pattern for components like avatars is to detect whether an image has been loaded to adapt the UI accordingly. It allows the component to react gracefully and prevent jarring changes or flashes of content.
Here's a simple React hook that tracks the loading state of an image. It was built by the awesome Radix UI team as useImageLoadingStatus
, I only made minor changes so all credit goes to them.
tsTry
export function useImageIsLoaded(src?: string) {const [isLoaded, setIsLoaded] = useState(false);useEffect(() => {if (!src) return setIsLoaded(false);let isMounted = true;const image = new window.Image();const createStatusHandler = (status: boolean) => () => {if (isMounted) setIsLoaded(status);};setIsLoaded(false);image.onload = createStatusHandler(true);image.onerror = createStatusHandler(false);image.src = src;return () => {isMounted = false;};}, [src]);return isLoaded;}
Then you can simply use it inside your component, like so:
tsxTry
const imageLoaded = useImageIsLoaded(imageUrl);
In action
I used this hook at Guide to ensure that the loading experience of the Avatar
component was smooth on fast and slow networks, even in the event of failure.
Here's what it looks like if you load it on a fast network:
And in a slow network:
Three important things are going on:
- On both fast and slow networks, the image is loaded all at once, instead of doing this:
-
Since the image will always load at least a bit after the component itself is rendered, even on fast networks, there's a fade-in effect to prevent a sudden flash.
-
Finally, since the image could take a while (or even fail) to load in slow networks, the avatar will fall back to displaying the initials. It doesn't happen instantly though: there's a small delay (one second in this case) so that the user doesn't see the initials flash if the image loads quickly.
All in all, this technique makes the experience for the user much more pleasant and significantly less jarring.
Deconstructing the hook
This hook is pretty clever and surprisingly simple.
It takes the target image URL (src
) and creates an Image
instance that uses it as source. This is functionally equivalent to creating an img
element (e.g. document.createElement('img')
) without adding it to the DOM.
tsTry
const image = new window.Image();// ...image.src = src;
Then, when the load
or error
events are triggered, the state is updated accordingly.
tsTry
image.onload = createStatusHandler(true);image.onerror = createStatusHandler(false);
Finally, the state is returned. If the image is loading or failed to load, the state will be false
. If it loaded successfully, the state will be true
.
tsTry
return isLoaded;
Neat, right?