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.
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);
});
}