Search code examples
javascriptreactjsdom-eventshtml5-audio

Audio event still firing off event after removed via removeEventListener


I've got the following two lifecycle methods in my React component, which is an audio track that contains an HTML <audio> element:

componentDidMount() {
  const {track} = this.props;

  this.refs.audio.src = track.getTrackUrl();
  _.each(this.audioEvents, (callback, eventName) => {
    this.refs.audio.addEventListener(eventName, callback.bind(this));
  });
}

componentWillUnmount() {
  _.each(this.audioEvents, (callback, eventName) => {
    console.info(`Removed ${eventName} from ${this.props.track.name}`);
    this.refs.audio.removeEventListener(eventName, callback.bind(this));
  });
}

I can see from the console.info call that all the events are being removed, yet I still get the following error when navigating off this particular route/page:

Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the undefined component.

This is happening because the pause callback is still getting fired off even after I've supposedly removed all event handlers:

pause() {
  console.log('pause got fired for', this.props.track.name);
  this.setState({playing: false});
},

What am I missing? I'm trying to do a nice clean-up when the component unmounts, but for some reason when I navigate off the page (causing the audio to stop playing), my event handler for pause still fires off.


Solution

  • When you use .bind you create a new instance of the function. So when you do this:

    this.refs.audio.addEventListener(eventName, callback.bind(this));
    

    You will no longer be able to remove that event listener because you didn't hold on to the reference to callback.bind(this). Here's how you fix it.

    componentDidMount() {
      const {track} = this.props;
      this.refs.audio.src = track.getTrackUrl();
      this.boundAudioEvents = _.mapValues(this.audioEvents, (callback, eventName) => {
        const boundEvent = callback.bind(this)
        this.refs.audio.addEventListener(eventName, boundEvent);
        return boundEvent;
      });
    }
    
    componentWillUnmount() {
      _.each(this.boundAudioEvents, (callback, eventName) => {
        console.info(`Removed ${eventName} from ${this.props.track.name}`);
        this.refs.audio.removeEventListener(eventName, callback);
      });
    }