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
https://goodreads.com)background-image css, 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!
My definition of valid is: the image has been loaded and it has both a width and a height greater than zero.
Now there are two ways you check if an image is valid:
img tag, then check if the image is valid.The following code will first assume that the image is not valid.
import React, { useEffect, useRef, useState } from "react";
const ImageComponent = ({src}) => {
    const container = useRef(null);
    const [valid, setValid] = useState(false);
    useEffect(() => {
        container.current.innerHTML = '';
        const image = new Image();
        const checkValid = () => {
            if (image.complete && image.naturalWidth > 0 && image.naturalHeight > 0) {
                setValid(true);
                container.current.appendChild(image);
            }
        }
        image.onload = checkValid;
        image.src = src;
    }, [src]);
    return (
        <div>
          <div ref={container} />
          {!valid && <div>Image not valid</div>}
        </div>
    );
};
Usage:
<ImageComponent src="IMAGE_URL" />
The following code will first assume that the image is valid.
import React, { useRef, useState } from 'react';
const ImageComponent = ({src}) => {
    const image = useRef(null);
    const [valid, setValid] = useState(true);
    const checkValid = () => {
        if (!image.current.complete || image.current.naturalWidth < 1 || image.current.naturalHeight < 1) setValid(false);
    }
    if (valid) {
        return (
            <img
                src={src}
                onLoad={checkValid}
                onError={() => setValid(false)}
                ref={image} 
            />
        );
    }
    return <div>Image not valid</div>;
};
Usage:
<ImageComponent src="IMAGE_URL" />
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With