reactjstypescriptyoutube-api

React Typescript Re-Render Youtube Frame


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

Solution

  • 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:

    Edit youtubeframe-forked-9hd7p3