138

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 error.

I would normally use the onerror method in the img tag but that doesn't seem to be working.

I am not sure how to do this with react.

Here is my component:

import React from 'react';
import {Link} from 'react-router';
import ContactStore from '../stores/ContactStore'
import ContactActions from '../actions/ContactActions';

class Contact extends React.Component {
  constructor(props) {
    super(props);
    this.state = ContactStore.getState();
    this.onChange = this.onChange.bind(this); 
 }

componentDidMount() {
  ContactStore.listen(this.onChange);
  ContactActions.getContact(this.props.params.id);
}

componentWillUnmount() {
  ContactStore.unlisten(this.onChange);
}

componentDidUpdate(prevProps) {
  if (prevProps.params.id !== this.props.params.id) {
    ContactActions.getContact(this.props.params.id);
  }
}

onChange(state) {
  this.setState(state);
}

render() {
  return (
    <div className='container'>
      <div className='list-group'>
        <div className='list-group-item animated fadeIn'>
          <h4>{this.state.contact.displayname}</h4>
          <img src={this.state.imageUrl} />
        </div>
      </div>
    </div>
  );
}
}

export default Contact;
Mogsdad
  • 44,709
  • 21
  • 151
  • 275
Joel Lawler
  • 1,409
  • 2
  • 10
  • 8
  • I ran into this problem, and I don't have the code to assist at the moment, but what I did was place the javascript checking in componentdidmount, which looks for an image error, if they occur, a callback is fired which replaces that image with the default image. – Chris Hawkes Dec 04 '15 at 21:35
  • Please, accept the answer by Georgii Oleinikov, because currently best scored answer may produce eternal loop and so is not good at all. – Mikhail Batcer Nov 29 '18 at 14:50
  • Found this video tutorial on this - https://youtu.be/90P1_xCaim4 which actually helped me building a full fledged image component for my application. I also found this along with an awesome preloaders for my image component - https://youtu.be/GBHBjv6xfY4. By combining both you can provide a wonderfull UX for the users. – Prem Jan 13 '19 at 16:19

28 Answers28

270

This works best for me

<img 
  src={record.picture}
  onError={({ currentTarget }) => {
    currentTarget.onerror = null; // prevents looping
    currentTarget.src="image_path_here";
  }}
/>
lbragile
  • 7,549
  • 3
  • 27
  • 64
Deepak Mallah
  • 3,926
  • 4
  • 21
  • 26
  • 55
    Seems like this method could lead to an infinite callback if "image_path_here" produces and error... – nano2nd Apr 20 '18 at 20:43
  • 4
    @tomhughes it will prevent infinite callbacks when "image_path_here" fails – Deepak Mallah Dec 26 '18 at 06:21
  • 4
    @DeepakMallah I followed your code ` {e.target.src = 'https://upload.wikimedia.org/wikipedia/en/c/c3/The_Martian_2014.jpg'; e.target.onError = null;}} />` However, the console in Safari still shows the error `Failed to load resource: the server responded with a status of 404 (Not Found)`. Is it normal? How to remove this console error? Thanks. – The questioner Sep 14 '19 at 14:11
  • Am getting the error: `Property 'onError' does not exist on type 'EventTarget'` – vizsatiz Nov 29 '21 at 08:40
  • 1
    @vizsatiz Try replacing `e.target` with `e.currentTarget` – lbragile Jan 03 '22 at 05:55
  • Could be even nicer if you could explain the code by comments. I mean you have but why would that prevent looping? What looping are you talking about in particular? – Steve Moretz Feb 17 '22 at 15:25
  • This results in the website permanently "loading" (spinning icon instead of favicon). At least on chrome – George B Mar 21 '22 at 20:18
  • 1
    I copied and pasted this in and added a console.log in the onError function, and it was continually running. I don't think the `currentTarget.onerror = null;` line prevents looping. – mmmm Jul 01 '22 at 02:27
  • 1
    @mmmm you are right, `e.target.onerror = null` doesn't stop the infinite loop. – user31782 Oct 04 '22 at 00:44
  • Then, does `currentTarget.onerror = null;` prevent the loop? – Luis Jan 27 '23 at 17:58
  • 1
    No, it doesn't @Luis – Gabriel Pellegrino Apr 16 '23 at 20:22
48

Since there is no perfect answer, I am posting the snippet I use. I am using reusable Image component that falls back to fallbackSrc.

Since the fallback image could fail again and trigger infinite loop of re-rendering, I added errored state.

import React, { Component } from 'react';
import PropTypes from 'prop-types';

class Image extends Component {
  constructor(props) {
    super(props);

    this.state = {
      src: props.src,
      errored: false,
    };
  }

  onError = () => {
    if (!this.state.errored) {
      this.setState({
        src: this.props.fallbackSrc,
        errored: true,
      });
    }
  }

  render() {
    const { src } = this.state;
    const {
      src: _1,
      fallbackSrc: _2,
      ...props
    } = this.props;

    return (
      <img
        src={src}
        onError={this.onError}
        {...props}
      />
    );
  }
}

Image.propTypes = {
  src: PropTypes.string,
  fallbackSrc: PropTypes.string,
};
isherwood
  • 58,414
  • 16
  • 114
  • 157
emil
  • 6,074
  • 4
  • 30
  • 38
  • 5
    A little note: not working if you're using React with server side rendering because images are being loaded asynchronously and by the time the hydration is made all errors will already have been triggered. – Liam Kernighan Oct 15 '19 at 19:18
  • I expanded this answer using TypeScript and adding Placeholder support https://stackoverflow.com/a/68378797/3457769 – sakramento Jul 14 '21 at 13:14
37

2021 Updated Answer using React Functional Components, Hooks and TypeScript

// ImageWithFallback.tsx
import React, { ImgHTMLAttributes, useState } from 'react'

interface Props extends ImgHTMLAttributes<any> {
  fallback: string
}

export default function ImageWithFallback({ fallback, src, ...props }: Props) {
  const [imgSrc, setImgSrc] = useState<string | undefined>(src)
  const onError = () => setImgSrc(fallback)

  return <img src={imgSrc ? imgSrc : fallback} onError={onError} {...props} />
}


Marcos
  • 1,043
  • 10
  • 15
33

You can use uncontrolled component:

<img src={this.state.img} ref={img => this.img = img} onError={
    () => this.img.src = 'img/default.img'
}>
K.Arthur
  • 331
  • 3
  • 3
16

It's So Simple

e.target.onerror = null If Error Image Also Fails to Load jsx

<img
        src={props.image}
        alt={props.title}
        onError={(e) =>
          (e.target.onerror = null)(
            (e.target.src =
              "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d3/Stadtbild_M%C3%BCnchen.jpg/2560px-Stadtbild_M%C3%BCnchen.jpg")
          )
        }
      />
TripleM
  • 1,096
  • 7
  • 20
Kabeer Jaffri
  • 652
  • 1
  • 9
  • 9
11

You need just define onError handler than change the state which will trigger component render method and eventually component will re-render with placeholder.

Please, don't use jQuery and React together!

import React from 'react';
import {Link} from 'react-router';
import ContactStore from '../stores/ContactStore'
import ContactActions from '../actions/ContactActions';

class Contact extends React.Component {
  constructor(props) {
    super(props);
    this.state = ContactStore.getState();
    this.onChange = this.onChange.bind(this); 
 }

componentDidMount() {
  ContactStore.listen(this.onChange);
  ContactActions.getContact(this.props.params.id);
}

componentWillUnmount() {
  ContactStore.unlisten(this.onChange);
}

componentDidUpdate(prevProps) {
  if (prevProps.params.id !== this.props.params.id) {
    ContactActions.getContact(this.props.params.id);
  }
}

onChange(state) {
  this.setState(state);
}

onError() {
  this.setState({
    imageUrl: "img/default.png"
  })
}

render() {
  return (
    <div className='container'>
      <div className='list-group'>
        <div className='list-group-item animated fadeIn'>
          <h4>{this.state.contact.displayname}</h4>
          <img onError={this.onError.bind(this)} src={this.state.imageUrl} />
        </div>
      </div>
    </div>
  );
}

export default Contact;
Skay
  • 9,343
  • 6
  • 27
  • 28
11

Arthur's answer will result in infinite callbacks if fallback image also fails.

To avoid that, first set a state in the constructor for imageLoadError as true :

constructor(props) {
    super(props);
    this.state = {
      imageLoadError: true,
    };
}

and then check for this state value in onError function to avoid infinite callbacks,

the code will look like this :-

<img
    src={"https://if_this_url_fails_go_to_onError"}
    onError={e => { 
        if(this.state.imageLoadError) { 
            this.setState({
                imageLoadError: false
            });
            e.target.src = 'fallbackImage.png';
        }
    }}
/>
Nitesh Ranjan
  • 1,221
  • 1
  • 13
  • 13
7

@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"; } }}
Georgii Oleinikov
  • 3,865
  • 3
  • 27
  • 27
6

Ran into a similar problem and the best solution i could find was Georgii Oleinikov's answer. (Doesn't require making new imageLoadError state as suggested by Nitesh Ranjan in his answer)

onError={(e)=>{ if (e.target.src !== "image_path_here"){
                    e.target.onerror = null;
                     e.target.src="image_path_here";}
                }
           }

e.target.onerror = null is not needed (and doesn't really help) because the if condition is enough to prevent the infinite loop(if backup image fails to load as well).

So:

onError={(e)=>{ if (e.target.src !== "image_path_here"){
                 e.target.src="image_path_here";}
               }
         }

EDIT: The other way around is to set a flag outside the return brackets and check for the flag in the if statement. Code should look something like this:

render(){
 let errorflag=true;
 return(
            <img alt='' src={imageUrl} 
                    onError={(e)=>{ if (errorflag){ errorflag=false; e.target.src=url; } }} />
            );
} 
Saurabh Chauhan
  • 151
  • 2
  • 5
5
import OriginalImage from '../../originalImg.png'
import ReplacementImage from '../../replaceImg.png'

<img
 src= OriginalImage
 alt="example"
 onError={(e) => {
    e.target.src = ReplacementImage //replacement image imported above
    e.target.style = 'padding: 8px; margin: 16px' // inline styles in html format
 }}
/>

this is what I'm currently using.

Mrinmay
  • 143
  • 1
  • 7
5

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} ... /></>
jtc
  • 71
  • 1
  • 4
4

I wrote like this.

import React, { useState } from 'react';
import NoImageSVG from './noImage.svg';

const ImgWithFallback: React.FunctionComponent<{ src: string; alt: string; className: string }> = ({
  src,
  alt,
  className,
}) => {
  const [isUndefined, updateIsUndefined] = useState(false);

  const onError = () => {
    updateIsUndefined(true);
  };

  if (isUndefined) {
    return (
      <div className={className}>
        <NoImageSVG width='5rem' height='5rem' />
      </div>
    );
  }

  return <img src={src} alt={alt} className={className} onError={onError} />;
};

export default React.memo(ImgWithFallback, () => true);

R. M.
  • 193
  • 1
  • 6
4

I used the above method of Arthurs making e.target.onerror = null to stop the infinite loop , but still the infinite loop happened. So , to stop the infinite loop I had to use the below method.I had to find the actual property onError and make it null.

<img src={imageSource}
     onError={(e) => { 
              e.target[Object.keys(e.target).filter(prop=>prop.includes('EventHandler'))[0]].onError = null;
              e.target.src = 'images/avatar.png'; }}
 />

event.target properties

3

I took @Skay's answer and created a reusable Image component. Posting in case it helps anyone:

import React, { PropTypes } from 'react';

const Image = ({src, fallbackSrc, ...other}) => {
    let element;
    const changeSrc = newSrc => {
        element.src = newSrc;
    };
    return (
        <img src={src} 
             onError={() => changeSrc(fallbackSrc)} 
             ref={el => element=el} 
             {...other} />
    );
};

Image.propTypes = {
    src: PropTypes.string,
    fallbackSrc: PropTypes.string
};
export default Image;
Kevin
  • 965
  • 6
  • 12
  • You shouldn't store state in the `` component, but rather in the containing `` component, which would mean refactoring it as a stateful component and using something like `this.setState({ src: defaultSrc })`. – James Conkling Feb 14 '17 at 19:25
3

Even though this is an old question if you are looking of a clean solution you can use react-image-fallback library.

<ReactImageFallback
                    src="my-image.png"
                    fallbackImage="my-backup.png"
                    initialImage="loader.gif"
                    alt="cool image should be here"
                    className="my-image" />

react-image-fallback

Chathurika Sandarenu
  • 1,368
  • 13
  • 25
  • 1
    The last published change to this repo was two years ago. An alternative is [react-image](https://github.com/mbrevda/react-image). – Raphael Jul 20 '20 at 16:51
  • using react-image should be accepted answer as it really solves all the problem that you may face when serving static build of react website – Alexander Cherednichenko Jul 23 '20 at 16:26
  • I achieved the goal using https://www.npmjs.com/package/react-image. Your answer inspired me to look for an npm instead of creating my own solution. Thanks a lot. :) – Anand Padiya Nov 29 '21 at 13:08
2

For those like me who also wanted to change the styles of the element and/or change the img source, just do something like this:

<img
  src={'original src url goes here'}
  alt="example"
  onError={(e) => {
     e.target.src = '/example/noimage.png' // some replacement image
     e.target.style = 'padding: 8px; margin: 16px' // inline styles in html format
  }}
/>

Hope it helps!

Gustavo Garcia
  • 521
  • 5
  • 5
2

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} />
Niks
  • 21
  • 3
1

For SSR (Server Side Rendering)...

So, here's a workaround that works (for me)!

const Img: FC<
  DetailedHTMLProps<ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>
> = ({ src, ...props }): JSX.Element => {
  const [hasRendered, setHasRendered] = useState(false);
  const imgRef = useRef<HTMLImageElement | null>(null);

  useEffect(() => {
    if (imgRef.current && hasRendered) {
      imgRef.current!.src = src || '';
    }
  }, [src, hasRendered]);

  useEffect(() => {
    setHasRendered(true);
  }, []);

  return (
    <img
      {...props}
      ref={imgRef as any}
      alt={props.alt || 'image'}
      aria-hidden={true}
      onError={...}
      onLoad={...}
    />
  );
};

So, the magic happens in the two useEffect hooks. (Using just one didn't work). Basically, the second useEffect ensures the first hook is triggered (or component re-renders) a second time (after initial render), due to the hasRendered dep, which then forces the image src to be set in that hook which then triggers the events on the client!

0

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}/>;
          } 
    }
0

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

S4beR
  • 1,872
  • 1
  • 17
  • 33
0

Previous versions have the bug; they don't count that src could be changed. So I made my ultimate solution and it:

  1. Supports typing
  2. Support case when src is changed
  3. Forwards ref
  4. Doesn't ignore onError (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}
      />
    );
  },
);
quolpr
  • 143
  • 1
  • 8
0

As it was mentioned in one of the comments, the best solution is to use react-image library. Using onError will fail when you try to serve static version of your react website after build.

Here is super simple and straightforward example how to use react-image, just import Img component

import {Img} from 'react-image'

And later specify a list of src that you try to load

<Img
   src={['images/image1.svg', 'images/default.svg']}
   alt="Some title"
/>   

If 1st url not found, the 2nd will be loaded, there are also some other pretty cool features like showing a spinner while image is loading or displaying some other component in case none of the listed images are available

0

Try this custom Image component:

import React, { useRef } from 'react';
import PropTypes from 'prop-types';

import defaultErrorImage from 'assets/images/default-placeholder-image.png';

const Image = ({ src, alt, className, onErrorImage }) => {
  const imageEl = useRef(null);
  return (
    <img
      src={src}
      alt={alt}
      className={className}
      onError={() => {
        imageEl.current.src = onErrorImage;
      }}
      ref={imageEl}
    />
  );
};

Image.defaultProps = {
  onErrorImage: defaultErrorImage,
};

Image.propTypes = {
  src: PropTypes.string.isRequired,
  alt: PropTypes.string.isRequired,
  className: PropTypes.string.isRequired,
  onErrorImage: PropTypes.string,
};

export default Image;
0

Typescript version:

const Avatar = (): JSX.Element => {
    function imageErrorHandler(e: React.SyntheticEvent<HTMLImageElement, Event>) {
      const el = e.target as HTMLImageElement
      el.onerror = null
      el.src = '/fallback.png'
    }

    return <img src={'/smth.png'} onError={imageErrorHandler}/>
  },
)

With forwardRef and possible null src:

import { forwardRef } from 'react'

type Props = Omit<React.ComponentPropsWithoutRef<'img'>, 'src'> & { src?: null | string }

const Avatar = forwardRef<HTMLImageElement, Props>(
  ({ src, ...rest }, ref): JSX.Element => {
    function imageErrorHandler(e: React.SyntheticEvent<HTMLImageElement, Event>) {
      const el = e.target as HTMLImageElement
      el.onerror = null
      el.src = '/fallback.png'
    }

    return <img src={src || '/alternative.png'} onError={imageErrorHandler} ref={ref} {...rest} />
  },
)
ZiiMakc
  • 31,187
  • 24
  • 65
  • 105
0

With the help of @emil's solution above I created this little functional component. It's using a fallback src on First error and removing the img on second error, from fallback src.

import React, { useState } from 'react'

function ImageFallback({ src, fallbackSrc, ...props }) {

    const [state, setState] = useState({ src: src, errored: false })
   

    //update(next img) state onMount 
    useEffect(() => {
       setState({
           src: src,
           errored: false,
       })

    }, [src])

   //update (remove) state onUnMount
   useEffect(() => {
       return () => {
           setState({
               src: null,
               errored: false,
           })
       }
   }, [])

    const onError = () => {
        //1st error
        if (!state.errored) {
            setState({
                src: fallbackSrc,
                errored: true,
            });
        } else if (state.errored && state.src) {
            //2nd error
            //when error on fallbacksrc - remove src
            setState({
                src: null,
                errored: true,
            });
        }

    }

    return (
        state.src && <img
            src={state.src}
            onError={onError}
            {...props}
        />
    )
}

export default ImageFallback

Usage ...

 <ImageFallback src={anySrc} fallbackSrc={anyFallbackSrc} className={classes.logo} alt='' />
0

I expanded @Emils solution using TypeScript and added

  • placeholder support while loading
import * as React from "react";

type Props = {
    src: string,
    fallbackSrc: string,
    placeholderColor?: string,
    className?: string,
}

type State = {
    src: string,
    errored: boolean,
    loaded: boolean
}

export default class Image extends React.Component<Props, State> {
    constructor(props: Props) {
        super(props);

        this.state = {
            src: props.src,
            errored: false,
            loaded: false
        };
    }

    onError = () => {
        if (!this.state.errored) {
            this.setState({
                src: this.props.fallbackSrc,
                errored: true,
            });
        }
    }

    onLoad = () => {
        if(!this.state.loaded){
            this.setState({loaded: true});
        }
    }

    render() {
        let style = {
            backgroundColor: this.props?.placeholderColor || "white"
        };

        if(this.state.loaded){
            style.backgroundColor = "transparent";
        }

        return (
            <img
                style={style}
                onLoad={this.onLoad}
                onError={this.onError}
                {...this.props}
                src={this.state.src}
            />
        );
    }
}
sakramento
  • 369
  • 5
  • 16
0

2022 Aug Updated Answer

el.target.onerror = null; //Not worked for me also not prevents looping.

The best solution is setting errcount attribute to the element.

Then check if attribute is set, no need to set src again.

also you can add logic to retry for 1,2,3...n times.

<img onError={this.addDefaultSrc} src={BaseURL + item.ImageUrl} />

addDefaultSrc(el) {
  var errcount = parseInt(el.target.getAttribute("errcount"));
  if (isNaN(errcount)) {
    el.target.setAttribute("errcount", "1");
    el.target.src = BaseURL + 'Images/no-image.jpg';
  }
  //else
  //el.target.src = "";
}
zubair Ahmad
  • 121
  • 3
  • 11
-2

this works for me .

{<img className="images"
    src={`/images/${student.src ? student.src : "noimage.png" }`} alt=  
{student.firstname} />} 

student is the name of my array and noimage the image, when there is no image is display.

Eric Aya
  • 69,473
  • 35
  • 181
  • 253