score:6

Accepted answer

these answers were helpful but there's a way to achieve this without needing to pass a key each time by taking advantage of the useeffect hook:

useeffect(() => {
    set_imgsrc(src);
}, [src]);

additionally, the onerror event doesn't seem to trigger for certain images (i believe layout='fill' doesn't trigger it in certain scenarios), for those cases i've been using the onloadingcomplete and then i check if the width of the image is 0

onloadingcomplete={(result) => {
    if (result.naturalwidth === 0) {  // broken image
        set_imgsrc(fallbacksrc);
    }
}}

full code:

import image from "next/image";
import { useeffect, usestate } from "react";

export default function imagefallback({ src, fallbacksrc, ...rest }) {
  const [imgsrc, set_imgsrc] = usestate(src);

  useeffect(() => {
    set_imgsrc(src);
  }, [src]);

  return (
    <image
      {...rest}
      src={imgsrc}
      onloadingcomplete={(result) => {
        if (result.naturalwidth === 0) {
          // broken image
          set_imgsrc(fallbacksrc);
        }
      }}
      onerror={() => {
        set_imgsrc(fallbacksrc);
      }}
    />
  );
}

score:10

@juliomalves has given 99% percent of the answer, however i would like to add to it. there is a problem when changing the src in his solution, as the image would not update because it's getting the imgsrc value which is not getting updated. this is my addition to his answer:

import react, { usestate } from 'react';
import image from 'next/image';

const imagefallback = (props) => {
    
    const { src, fallbacksrc, ...rest } = props;
    const [imgsrc, setimgsrc] = usestate(false);
    const [oldsrc, setoldsrc] = usestate(src);
    if (oldsrc!==src)
    {
        setimgsrc(false)
        setoldsrc(src)
    }
    return (
        <image
            {...rest}
            src={imgsrc?fallbacksrc:src}
            onerror={() => {
                setimgsrc(true);
            }}
        />
    );
};

export default imagefallback;

now imgsrc is used only as a flag, and there is tracking of src value, which helps to change the image even if you had an image that had the fallback image on before.

score:27

you can create a custom image component that extends the built-in next/image and adds the fallback logic if the image fails to load by triggering the onerror callback.

import react, { usestate } from 'react';
import image from 'next/image';

const imagewithfallback = (props) => {
    const { src, fallbacksrc, ...rest } = props;
    const [imgsrc, setimgsrc] = usestate(src);

    return (
        <image
            {...rest}
            src={imgsrc}
            onerror={() => {
                setimgsrc(fallbacksrc);
            }}
        />
    );
};

export default imagewithfallback;

then, you can directly use the custom component instead of next/image as follows:

<imagewithfallback
    key={videoid}
    layout="fill"
    src={`https://i.ytimg.com/vi/${videoid}/maxresdefault.jpg`}
    fallbacksrc={`https://i.ytimg.com/vi/${videoid}/hqdefault.jpg`}
/>

passing a key prop to trigger a re-render on videoid change.


Related Query

More Query from same tag