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:
What I have tried:
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.