Using YouTube IFrame API in React

As you know, videos on YouTube can be embedded on your websites. Furthermore, you can use IFrame API provided by YouTube to be gained granular controls for those videos, even in React projects.

Type Definitions for IFrame API

Before get into the React code, I'd highly recommend to set up TypeScript to safely use this API. Type Definitions for this API is available on npm.

npm install --save-dev @types/youtube

After you install this package, the YT namespace is globally available on your TypeScript project and the player object is instantiated with YT.Player.

The Context Provider

To ensure that IFrame API code is loaded before player objects are created, you need to tell every <YouTubePlayer> components the API code is loaded.

There are two state variables called isMounted and isApiReady.

The onYouTubeIframeAPIReady function is called when the IFrame API is ready to be used. The isApiReady is set to true inside this function.

The isMounted is set to true simultaneously with the definition of the onYouTubeIframeAPIReady function, because the API code expects that the onYouTubeIframeAPIReady function is defined in advance.

The YouTubeContext can be a provider of itself in React 19.

// YouTubeProvider.tsx
"use client";

import { createContext, useEffect, useState } from "react";

declare global {
  interface Window {
    onYouTubeIframeAPIReady(): void;
  }
}

export const YouTubeContext = createContext({
  isApiReady: false,
});

export function YouTubeProvider({ children }: { children: React.ReactNode }) {
  const [isMounted, setIsMounted] = useState(false);
  const [isApiReady, setIsApiReady] = useState(false);

  useEffect(() => {
    window.onYouTubeIframeAPIReady = () => {
      setIsApiReady(true);
    };
    setIsMounted(true);
  }, []);

  return (
    <YouTubeContext value={{ isApiReady }}>
      {isMounted && <script src="https://www.youtube.com/iframe_api" async />}
      {children}
    </YouTubeContext>
  );
}

Note that in React 19, the <script> can be located everywhere in your app, and the <script> will be shifted inside a <head>. React de-duplicates <script> elements if the src and the async={true} props are passed.

The Player Component

Let's create the <YouTubePlayer> component!

Exposing Functions regarding a Playback.

To control the playback of videos, the playVideo function and the pauseVideo function need to called in parent components of the <YouTubePlayer> component. One of approaches is making these functions are available via the ref.

The useImperativeHandle hook is used to expose the playVideo function and the pauseVideo function outside the component. The useImperativeHandle hook can customize the ref object.

The player object is created if the isApiReady is set to true.

Since React 19, we can pass ref as a prop without forwardRef, and consume a context with use rather than useContext hook.

// YouTubePlayer.tsx
import { use, useEffect, useImperativeHandle, useRef } from "react";
import { YouTubeContext } from "@/providers/YouTubeProvider";

interface YouTubePlayer {
  playVideo(): void;
  pauseVideo(): void;
}

export default function YouTubePlayer({
  ref,
  options,
}: {
  ref: React.RefObject<YouTubePlayer | null>;
  options: YT.PlayerOptions;
}) {
  const { isApiReady } = use(YouTubeContext);
  const playerRef = useRef<YT.Player | null>(null);
  const elementRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!isApiReady) return;
    if (!elementRef.current) return;

    playerRef.current = new YT.Player(elementRef.current, options);

    return () => {
      playerRef.current?.destroy();
    };
  }, [isApiReady, options]);

  useImperativeHandle(ref, () => {
    return {
      playVideo() {
        playerRef.current?.playVideo();
      },
      pauseVideo() {
        playerRef.current?.pauseVideo();
      },
    };
  }, []);

  return <div ref={elementRef} />;
}

Using the Player Component.

Finally, you can use <YouTubePlayer> component, and control the video playback via the ref object.

"use client";

import { useRef } from "react";
import YouTubePlayer from "@/components/YouTubePlayer";

export default function Home() {
  const playerRef = useRef<React.ComponentRef<typeof YouTubePlayer>>(null);

  return (
    <main>
      <h1>YouTube IFrame Player API</h1>
      <YouTubePlayer
        ref={playerRef}
        options={{
          videoId: "T_WSXXPQYeY",
          playerVars: { origin: "http://localhost:3000" },
        }}
      />
      <button type="button" onClick={() => playerRef.current?.playVideo()}>
        Play
      </button>
      <button type="button" onClick={() => playerRef.current?.pauseVideo()}>
        Pause
      </button>
    </main>
  );
}

The example code is here.

For more information about IFrame API, please read the API reference.