reactjshtml5-videovideo.jsvideo-player

React: Switching Between Custom React Video Player and Video.js Causes "Failed to Execute 'removeChild'" Error


I'm working on a video player component in React where I switch between a custom video player and Video.js using a toggle. The initial load of the custom player works fine, and when I switch to Video.js, it also works perfectly. However, when I try to switch back to my custom player, I receive the following error:

Uncaught runtime errors: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.

Here is the relevant part of my code:

const videoRef = useRef(null);
const canvasRef = useRef(null);
const playerRef = useRef(null);
const [isCustomPlayer, setIsCustomPlayer] = useState(true);

const handleTogglePlayer = () => {
  setIsCustomPlayer((prev) => !prev);
};

useEffect(() => {
  if (videoRef.current) {
    if (isCustomPlayer) {
      if (playerRef.current) {
        playerRef.current.dispose(); // Disposing Video.js instance
        playerRef.current = null; // Resetting the reference
      }
      videoRef.current.src = videoData?.video_link || '';
    } else {
      if (!playerRef.current) {
        playerRef.current = videojs(videoRef.current, {
          controls: true,
          autoplay: false,
          preload: "auto",
          sources: [{
            src: videoData?.video_link,
            type: 'video/mp4'
          }]
        });
      }
    }
  }

  return () => {
    if (playerRef.current) {
      playerRef.current.dispose();
      playerRef.current = null; 
    }
  };
}, [isCustomPlayer, videoData]);

<div style={{ position: "relative" }}>
  {isCustomPlayer ? (
    // Custom player
    <video
      key="custom-player"
      id="video-player"
      className={
        isShortVideo ? "short-video-container" : "video-container"
      }
      ref={videoRef}
      src={videoData?.video_link}
      controls
      controlslist="nodownload"
      disablePictureInPicture
    ></video>
  ) : (
    // Video.js player
    <video
      key="video-js-player"
      id="video-player"
      ref={videoRef}
      className={`video-js vjs-default-skin vjs-big-play-centered ${
        isShortVideo ? "short-video-container" : "video-container"
      }`}
      controls
    ></video>
  )}
  <canvas ref={canvasRef} style={{ display: "none" }} />

className="btn btn-toggle-player"
  onClick={handleTogglePlayer}
>
  Switch to {isCustomPlayer ? "Video.js" : "Custom"} Player
</button>

Issue:

  1. The custom player loads and works fine.
  2. Switching to Video.js works without issues.
  3. When switching back to the custom player, I get the error Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.

What I have tried:


Solution

  • I found the solution. The issue was due to the use of a Fragment (<>) around my video players. Since Fragment doesn’t create an actual DOM element, React wasn’t able to properly manage the DOM when switching between Video.js and the custom player, especially with the way Video.js manipulates the DOM.

    I replaced the Fragment with a div wrapper around each player. This gave React a proper DOM element to manage and allowed the switching between players to work without errors.

    <div style={{ position: "relative" }}>
      {isCustomPlayer ? (
        <div key="custom-player">
          <video
            id="video-player"
            ref={videoRef}
            className={isShortVideo ? "short-video-container" : "video-container"}
            src={videoData?.video_link}
            controls
            controlslist="nodownload"
            disablePictureInPicture
          ></video>
        </div>
      ) : (
        <div key="video-js-player">
          <video
            id="video-player"
            ref={videoRef}
            className={`video-js vjs-default-skin vjs-big-play-centered ${
              isShortVideo ? "short-video-container" : "video-container"
            }`}
            controls
          ></video>
        </div>
      )}
    </div>
    

    Why it works:

    Switching to a div instead of a Fragment creates a proper DOM wrapper around each player, allowing React to handle the mounting and unmounting process more effectively. This ensures that any DOM manipulations done by Video.js are properly cleaned up when switching back to the custom player.