Imagine that I want to create a component to render an image. If the image resource is valid, I should render it into a div via css background-image. Otherwise I should render it as a standard <img> with an alt tag.
So we get something like this:
const ImageComponent = ({src}) => {
const [loaded, setLoaded] = useState(false);
const [renderFallback, setRenderFallback] = useState();
useEffect(() => {
const image = new Image();
image.onsuccess = () => {
setLoaded(true);
setRenderFallback(false);
}
image.onerror = () => {
setLoaded(true);
setRenderFallback(true);
}
image.src = src;
return () => {
// clean up listeners
image.onsuccess = undefined;
image.onerror = undefined;
}
}, [src]);
if (!loaded) { return null; }
return renderFallback ?
<div style={{backgroundImage: `url(${src})`}}/> :
<img src={src} alt="my alt"/>;
}
Here, we first kick off a fetch for the image via plain javascript (within useEffect). If it's valid, the resource is cached (by the browser), and the rendering of the subsequent <div> is instantaneous.
However, if the resource is not valid, then there is no caching mechanism in place (see this question for more details). And so when we render the new <img> element at the end of the Component, it kicks off a brand new request to fetch that image (despite deducing that it's broken already).
So, to avoid this duplicate fetching, a possible solution would be to render the already-initialized image element, rather than a new react element to represent that image.
Is this possible?
Feedback from comments
- An invalid image is one that returns a status of 404, or points to a resource that's not actually an image (e.g.
https://goodreads.com) - I would like to display the browser's default 'broken image' in cases where it's broken. This can't be done via
background-imagecss, so I need to rely on a standard<img>element. As I've already determined that the image is broken (in useEffect), I'd like to avoid a new fetch when I'm rendering the broken<img>in React.
Hope that clarifies things!