I have a react component that is the detail view from a list.
I am trying to replace the image with a default image if the image does not exist and there is a 404 er
You can use object
if that's ok with your requirement. Something like below will work perfectly fine
<object data={expected_image} type="image/jpg">
<img src={DEFAULT} alt="404" />
</object>
Check this answer for more details https://stackoverflow.com/a/29111371/1334182
@DepH's answer is nice, but it does produce and infinite loop if your error source also doesn't load. This helped me avoid the callback loop:
onError={(e)=>{ if (e.target.src !== "image_path_here")
{ e.target.onerror = null; e.target.src="image_path_here"; } }}
Here's an answer using hooks:
import React, { useState } from 'react'
/**
* Returns an object that can
* be spread onto an img tag
* @param {String} img
* @param {String} fallback
* @returns {Object} { src: String, onError: Func }
*/
function useFallbackImg(img, fallback) {
const [src, setImg] = useState(img)
function onError(e) {
console.log('Missing img', img, e)
// React bails out of hook renders if the state
// is the same as the previous state, otherwise
// fallback erroring out would cause an infinite loop
setImg(fallback)
}
return { src, onError }
}
/**
* Usage <Image src='someUrl' fallback='fallbackUrl' alt='something' />
*/
function Image({src, fallback, ...rest}) {
const imgProps = useFallbackImg(src, fallback)
return <img {...imgProps} {...rest} />
}
And if you are want to handle the src
prop changing, you can pass a key
prop of the src
.
https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key
<Image key='someUrl' src='someUrl' fallback='fallbackUrl' alt='...' />
The only extreme contrived edge case where using a key like this might fail is with sibling components. I think only one sibling node will render if they have the same key. To get around this you could probably wrap the Image in a <>
Fragment
.
<><Image key={srcProp} ... /></>
<><Image key={srcProp} ... /></>
That's how I did it.
class Pix extends React.Component{
constructor(props){
super(props);
this.state={link: this.props.link};
this.onError=this.onError.bind(this);
}
onError(){
console.log("error: could not find picture");
this.setState(function(){ return {link: "missing.png"}; });
};
render(){
return <img onError={this.onError} src={this.state.link}/>;
}
}
If anyone is using image src with require then onError doesn't work as -
<img src={require(`./../../assets/images/${props.imgName}.png`)} className="card-img" alt={props.name} />
then require throws an error, where I tried multiple ways and came to try and catch block solution as -
let imgSrc;
try {
imgSrc = require(`./../../assets/images/${props.imgName}.png`);
} catch {
imgSrc = require(`./../../assets/images/default.png`);
}
and use as
<img src={imgSrc} className="card-img" alt={props.name} />
Previous versions have the bug; they don't count that src
could be changed. So I made my ultimate solution and it:
src
is changedonError
(means you can pass onError
to ImageWithFallback
like you usually do with <img />
) Here it is:
import React, { useState, useCallback, useEffect } from 'react';
import noImage from 'src/svg/no-image.svg';
export const ImageWithFallback = React.forwardRef(
(
{
onError,
...props
}: React.DetailedHTMLProps<
React.ImgHTMLAttributes<HTMLImageElement>,
HTMLImageElement
>,
ref: React.Ref<HTMLImageElement>,
) => {
const [imageLoadFailed, setImageLoadFailed] = useState<boolean>(false);
const handleError = useCallback(
(e: React.SyntheticEvent<HTMLImageElement, Event>) => {
if (imageLoadFailed) return;
setImageLoadFailed(true); // to avoid infinite loop
if (onError) {
onError(e);
}
},
[imageLoadFailed, setImageLoadFailed, onError],
);
useEffect(() => {
setImageLoadFailed(false); // in case `src` is changed
}, [props.src]);
return (
<img
{...props}
src={imageLoadFailed ? noImage : props.src}
onError={handleError}
ref={ref}
/>
);
},
);