Problem
We often run into a use case where the browser takes a lot of time to load an image, and you can't really do anything but wait. While the image is being loaded, the UI looks broken for the end user, and then suddenly the image shows up. To add to this, if you don't handle a fixed size for the image container, the UI elements might shift after the image is loaded, which creates a very bad user experience.
A few reasons why the image might take longer to load:
- Unoptimized images (higher resolution images)
- Slow internet connection
Solution: useImage
The main objective here is to track loading, loaded, and error states so that we can show a loader or maybe a shimmer effect.
We define three statuses: LOADING, LOADED, and FAILED.
In the hook implementation:
- We make an image object using the
Imageconstructor. - We listen to the onload event which fires immediately after the browser loads the object and the onerror event which fires when an error occurs during object loading.
- We have a ref that contains the loaded image, where we'll find important properties like the image's actual height and width (
naturalHeight,naturalWidth).
Usage
We destructure status and imgRef from the hook. Based on the status, we conditionally render a skeleton loader, an error placeholder, or the actual image element.
Improvements
At this point, we've achieved our goal of handling the loading/loaded/error states of the image.
But this way of handling the status is a bit tedious. Let's improve this.
Simple Way: Write a wrapper for the Image component that internally handles image states, and customize it by passing the loader and error placeholders as props.
This creates a reusable ImageComponent that handles all the state management internally while allowing customization through props.