https://codesandbox.io/p/sandbox/s82tlh?file=%2Fsrc%2FApp.tsx%3A14%2C1
I created a Youtube Player Frame and im using it inside of my Player React.Component. The first time where i call the Component, the Player loads normal but if I switch the component and go a second time to the Player Component, the Youtube Player Frame is not rendered...
declare global {
interface Window {
onYouTubeIframeAPIReady: () => void;
YT: any;
}
}
import React, { useEffect, useRef } from 'react';
interface PlayerFrameProps {
videoId: string;
width: string;
height: string;
onStateChange?: () => void;
}
const PlayerFrame: React.FC<PlayerFrameProps> = ({ videoId, width, height, onStateChange }) => {
const playerRef = useRef(null);
useEffect(() => {
const loadYouTubeAPI = () => {
if (!document.getElementById('youtube-api')) {
const tag = document.createElement('script');
tag.id = 'youtube-api';
tag.src = "https://www.youtube.com/iframe_api";
const firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
}
};
loadYouTubeAPI();
window.onYouTubeIframeAPIReady = () => {
playerRef.current = new window.YT.Player('player', {
height: height,
width: width,
videoId: videoId,
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
};
const onPlayerReady = (event: any) => {
event.target.playVideo();
};
const onPlayerStateChange = (event: any) => {
if (event.data === window.YT.PlayerState.ENDED) {
if (onStateChange) {
onStateChange();
}
}
};
return () => {
if (playerRef.current) {
playerRef.current.destroy();
}
};
}, []);
useEffect(() => {
if (playerRef.current && playerRef.current.loadVideoById) {
playerRef.current.loadVideoById(videoId);
}
}, [videoId]);
return (
<div>
<div id="player"></div>
</div>
);
};
export default PlayerFrame;
;
export default PlayerFrame;
Edit: I changed the Code and now the player dont gets destroyd if I change the VideoID but still the same problem, when I switch to another Component the player gets destroyed and dont rerender if I open the Component again
import React, { useEffect, useRef } from 'react'
import PlayerFrame from './PlayerFrame';
...
interface State {
...
}
export default class Player extends React.Component<any, State> {
constructor(props: any) {
super(props);
this.state = {
...
}
}
componentDidMount() {
this.getData();
}
handlePlayerStateChange = () => {
this.setState((prevState) => ({
playlist: prevState.playlist.slice(1),
}));
}
public render() {
const { isLoading, playlist, settingsGeneral } = this.state;
const currentVideoID = playlist.length > 0 ? playlist[0].videoID : '';
return (
<>
{isLoading ? (
<div><Spin></div>
) : (
<>
<PlayerFrame
videoId={currentVideoID}
width={settingsGeneral.playerWidth.toString()}
height={settingsGeneral.playerHeight.toString()}
onStateChange={this.handlePlayerStateChange}
/>
</>
)}
</>
);
}
...
}
The way I see it, you first initialize it on this event window.onYouTubeIframeAPIReady
which is not called on re-render. So I think you could just reinitialize the playerRef.current
again after re-render, and trigger it by detecting if youtube-api
script has been injected. So you can first give data-attribute to the script:
tag.id = "youtube-api";
tag.src = "https://www.youtube.com/iframe_api";
tag.dataset.name = "youtube-api"; // add this
Then you can reinitialize it this way:
if (
window.YT &&
document.querySelector('script[data-name="youtube-api"]')
) {
playerRef.current = new window.YT.Player("player", {
height: 400,
width: 400,
videoId: videoId,
events: {
onReady: onPlayerReady,
},
});
}
Forked sandbox: