2

I'm in a bit of a pickle here, I've made myself a simple component that loads images asynchronously and displays them when ready. My issues is that I somehow am not removing the event listeners properly, because React complains that it cannot set state on an unmounted component.

My code:

export default class Image extends React.Component {
  constructor() {
    super()
    this.state = {
      preloadReady: false,
      sourceReady: false,
      img1: new window.Image(),
      img2: new window.Image(),
    }
  }
  load() {
    let preload = this.refs.preloadElement,
        src = this.refs.completeElement,
        self = this, ctx1, ctx2, img1, img2, load;

        ctx1 = preload.getContext('2d');
        ctx2 = src.getContext('2d');

        this.state.img1.addEventListener('load', () => {
            load(this.state.img1, ctx1, preload)
            this.setState({preloadReady: true});
        })


        this.state.img1.crossOrigin = "anonymous";
        this.state.img1.src = this.props.preload;

        this.state.img2.crossOrigin = "anonymous";

        this.state.img2.addEventListener('load',
            () => {
                setTimeout( () => {load(this.state.img2, ctx2, src)
                this.setState({sourceReady: true});
            }, 100)
        })

        this.state.img2.src = this.props.src;

        load = function(img, ctx, canvas) {
            var data, filtered;

            canvas.width = img.width;
            canvas.height = img.height;
            ctx.drawImage(img ,0 ,0 ,img.width,img.height,0,0, canvas.width, canvas.height);

        }
  }
  componentDidMount() {
    this.load();
  }
  componentWillUnmount() {
    // The following does not work
    this.state.img1.removeEventListener('load');
    this.state.img2.removeEventListener('load');
  }
  render () {

    let classes = {
      src: '',
      pre: ''
    }

    if (this.state.preloadReady) classes.pre = 'ready';
    if (this.state.sourceReady) classes.src = 'ready';

    return (
      <div class="async-image tr_quick" style={{width: this.props.width, height: this.props.height}}>
          <canvas ref="preloadElement" style={{width: this.props.width, height: this.props.height}} class={'preload tr_quick ' + classes.pre}></canvas>
          <canvas ref="completeElement" style={{width: this.props.width, height: this.props.height}} class={'src tr_quick ' + classes.src}></canvas>
      </div>
    )
  }
}

As you can see I'm trying to remove the listeners in componentWillUnmount, but it says I am missing an argument?

1 Answer 1

3

You shouldn't put your images into state, instead define images like this:

constructor(props, context) {
  super(props, context);
  this.img1 = new window.Image();
  this.img2 = new window.Image();
}

and instead adding event listener you can use the image's onload event:

onImageLoaded(imgType) {
  if (imgType === 'pre') {
    ...  
  }
  ...
}

this.img1.onload = this.onImageLoaded.bind(this, 'pre');
this.img2.onload = this.onImageLoaded.bind(this, 'real');
...

then remove the onload listener by assign null to it:

this.img1.onload = null;

and if you use some timers, always remove the timer in componentWillUnmount() like:

// Create timer..
this.timer1 = setTimeout(() => {...}, 100);

componentWillUnmount() {
  // Remove 
  clearTimeout(this.timer1);
}
Sign up to request clarification or add additional context in comments.

7 Comments

What does this line do? this.onImageLoaded = this.onImageLoaded.bind(this); I'm a bit new to react, though I got most of it down, I've never seen anything like that before.
it binds this to onImageLoaded function so you can then call this.dosomething (access component variables and methods) inside that onImageLoaded function. If you don't bind the function like that, calling for example this.img1 inside that function would throw error. Bind is not react stuff.. it's just javascript function.
Ah! That's neat. I'm just a little bit confused, because my two event listeners both have slightly different actions as in I need to pass it arguments, can I do that? Edit: For example, I'm having a 10x10 preloaded image that loads first, and I am blurring it for a nicer look. I'm obviously not blurring the actual finished image though.
Is there a reason to use canvas in your component? I would do (and have done) this same kind of preloader with just one image (the "real" one) and use some css animation as an preloader.. it's not reasonable to use some other image as preloader, since it takes then time to load that preloader image :)
I'm using canvas because that allows me to do more dynamic stuff, such as blurring the image. And for your information, the preloaded image is super compressed and is hardly over 300 bytes, so that's not a problem.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.