Search code examples
reactjsnext.jsvideoreact-ref

React(Next.js) useRef question with video (npm run build vs npm run dev)


I can't figure out how to prevent the playerRef from reloading when clipLink change.

When i work on dev mode(npm run dev), i can switch videos without reloading playerRef. However, when i work on production mode(npm run build && npm start), i can't switch videos without reloading playerRef.

Please assume that there's a button in the Parent Component that changes the clipLink when clicked(I removed it from the code in here for simplicity)

This is Parent Component

import { useRef } from "react";
import { YouTubePlayer } from 'react-youtube';
import ChildPage from "@/components/ChildPage";

const ParentPage = () => {

  const playerRef = useRef<YouTubePlayer | null>(null);

  return (
    <>
      <ChildPage
        clipLink={clipLink}
        playerRef={playerRef}
      />
    </>
  );
};

export default ParentPage;

This is Child Component

import { useEffect } from "react";
import YouTube, { YouTubeProps } from 'react-youtube';

const ChildPage = ({ clipLink, playerRef }) => {

  const onPlayerReady: YouTubeProps['onReady'] = (event) => {
    playerRef.current = event.target;

    event.target.playVideo();
    event.target.setVolume(30);
  };

  const onPlayerStateChange: YouTubeProps['onStateChange'] = (event) => {
    const playerState = event.data;

    switch (playerState) {
      case 0:
        event.target.playVideo();
        break;
      default:
        console.log('Player state changed to:', playerState);
    }
  };

  const opts: YouTubeProps['opts'] = {
    playerVars: {
      autoplay: 1,
      controls: 1,
      fs: 0,
      modestbranding: 1,
      rel: 0,
      iv_load_policy: 3,
    },
  };

  useEffect(() => {
    if (clipLink && playerRef.current) {
      try {
        playerRef.current.loadVideoById(clipLink);
        playerRef.current.playVideo();
        playerRef.current.setVolume(30);
      } catch (error) {
        console.error('Error loading video -', error);
      }
    }
  }, [clipLink]);


  return (
    <>
      <YouTube
        videoId={clipLink}
        opts={opts}
        onReady={onPlayerReady}
        onStateChange={onPlayerStateChange}
      />
    </>
  );
};

export default ChildPage;

As you can see, in the Child component, I have playerRef.current.loadVideoById(clipLink);.

I thought this code would change the video of playerRef without reloading, and it really worked as intended in dev mode(npm run dev).

However, in production mode(npm run build && npm start), it doesn't work as expected...


Solution

  • The issue you're experiencing stems from how React handles component re-renders and the YouTube library. When the clipLink changes, React unmounts and remounts the YouTube player in production mode, causing the playerRef to reset. In development mode, React’s hot-reloading behaviour might suppress some re-mounting behaviours, which can explain the discrepancy.

    To fix this, you must ensure the YouTube player remains mounted, even when the clipLink changes. Instead of unmounting and remounting the player, you can update the video being played using the player's loadVideoById method.

    import { useEffect } from "react";
    import YouTube, { YouTubeProps } from 'react-youtube';
    
    const ChildPage = ({ clipLink, playerRef }) => {
      const onPlayerReady: YouTubeProps['onReady'] = (event) => {
        playerRef.current = event.target;
    
        event.target.playVideo();
        event.target.setVolume(30);
      };
    
      const onPlayerStateChange: YouTubeProps['onStateChange'] = (event) => {
        const playerState = event.data;
    
        switch (playerState) {
          case 0:
            event.target.playVideo();
            break;
          default:
            console.log('Player state changed to:', playerState);
        }
      };
    
      const opts: YouTubeProps['opts'] = {
        playerVars: {
          autoplay: 1,
          controls: 1,
          fs: 0,
          modestbranding: 1,
          rel: 0,
          iv_load_policy: 3,
        },
      };
    
      // UseEffect to update the video when clipLink changes
      useEffect(() => {
        if (playerRef.current && clipLink) {
          const videoId = new URLSearchParams(new URL(clipLink).search).get('v');
          if (videoId) {
            playerRef.current.loadVideoById(videoId);
          }
        }
      }, [clipLink, playerRef]);
    
      return (
        <YouTube
          videoId={new URLSearchParams(new URL(clipLink).search).get('v') || ""}
          opts={opts}
          onReady={onPlayerReady}
          onStateChange={onPlayerStateChange}
        />
      );
    };
    
    export default ChildPage;