I have a React component that uses the https://github.com/selz/plyr media player. Everything works fine until the component unmounts, which results in an error from the Vimeo API. Specifically: Uncaught (in promise) TypeError: Cannot read property 'postMessage' of null
.
After this error occurs, I try to load the module again it will fail due to this.player
being undefined
, but if you destroy that, and try again it will load. Perhaps the React Tree is saving the first iteration of the component, and I need to completely destroy it somehow?
Here's my component:
import React, {Component, PropTypes} from 'react';
import {findDOMNode} from 'react-dom';
import plyr from 'plyr';
/**
* @desc Regex to retrieve the Vimeo video ID from the URL.
* @type {RegExp}
*/
const regex = /^.*(vimeo\.com\/)((channels\/[A-z]+\/)|(groups\/[A-z]+\/videos\/))?([0-9]+)/g;
export default class Vimeo extends Component {
static propTypes = {
url: PropTypes.string.isRequired,
};
constructor(props) {
super(props);
// Use regex to get the video id from the url
this.videoId = regex.exec(this.props.url);
}
/*
|--------------------------------------------------------------------------
| Digest Cycle
|--------------------------------------------------------------------------
*/
/**
* @desc Initiate video player.
*/
componentDidMount() {
this.player = plyr.setup(findDOMNode(this), {
controls: [],
autoplay: true,
loop: true,
mute: true,
});
this.player[0].on('ready', () => {
// Mute the video
if (!this.player[0].isMuted()) {
this.player[0].toggleMute();
}
});
}
/**
* @desc Destroy video player
*/
componentWillUnmount() {
this.player[0].destroy();
}
/*
|--------------------------------------------------------------------------
| Render
|--------------------------------------------------------------------------
*/
render() {
let player = null;
if (typeof this.videoId !== 'undefined' && this.videoId !== null) {
player = (
<div>
<div
data-type="vimeo"
data-video-id={this.videoId}
/>
</div>
);
}
return player;
}
}
Your problem is being caused by the way in which the Plyr object gets destroyed. Take a look at the
_destroy()
method, which is what is exposed in the Plyr api.https://github.com/Selz/plyr/blob/master/src/js/plyr.js#L3234
As you can see, in the case of Vimeo, this line
plyr.embed.unload().then(cleanUp);
and the following call tosetTimeout
make it clear that_destroy
is not a synchronous call.First,
plyr.embed.unload()
makes a call to this:https://github.com/vimeo/player.js/blob/e0f607196f7b38e3a9891e70dda38da2731cff79/src/player.js#L440
which returns a Promise. Once the promise is fulfilled, the
cleanup
function in Plyr gets executed, and that is when the DOM gets updated and variables reset. What this means is that your component will unmount without waiting for the Plyr instance to be fully restored to default or "destroyed". This may cause unexpected behaviour if you re-mount the component right away.Inside
componentWillUnmount
, I suggest that you do not rely ondestroy()
to update the DOM and reset the Plyr instance. You can instead delete the DOM element wrapping the video embed and just setthis.player
tonull
so the Plyr instance inside gets nuked and collected by the gc. The next time your component gets mounted, the DOM elements that were holding the video wont be there and your player will initialize normally, setting a new Plyr instance and creating the necessary html again.