Creating a Reusable Fullscreen Hook in React

Recently, I was working on a React project that required certain elements to toggle fullscreen mode, and I needed to reuse this functionality across different components. I wanted a clean, reusable solution that leveraged TypeScript to ensure type safety. This led me to create a custom hook for managing fullscreen behavior in React. In this post, I’ll walk through how I built a useFullscreen hook, which allows you to toggle fullscreen mode on any element, all while keeping the logic clean and reusable.

Thank me by sharing on Twitter 🙏

Why Use a Custom Fullscreen Hook?

React hooks are a powerful way to abstract and reuse logic. If you’re working on a web app where fullscreen functionality is required for multiple components—whether it’s an image gallery, video player, or custom modal—using a custom hook can save time and simplify your codebase.

In this guide, we’ll focus on building a useFullscreen hook from scratch, allowing you to toggle fullscreen mode for any element while handling browser compatibility and managing fullscreen state efficiently.

Setting Up the useFullscreen Hook

To start, we need to create a hook that encapsulates the logic for requesting and exiting fullscreen mode. Since browsers implement fullscreen functionality differently, we’ll handle compatibility for Safari and Internet Explorer, which use vendor-prefixed methods like webkitRequestFullscreen and msRequestFullscreen.

We’ll also track whether the current element is in fullscreen mode and update this state when the user enters or exits fullscreen.

1. Creating the Hook Structure

First, let’s create a basic structure for the hook. We’ll use TypeScript, which ensures type safety and prevents potential runtime errors. In TypeScript, we can use the useRef hook to track the element we want to make fullscreen, and we’ll store the fullscreen state with the useState hook.

Here’s the initial setup:

TypeScript
import { useRef, useCallback, useState, useEffect } from "react";

const useFullscreen = () => {
  const fullscreenRef = useRef<HTMLDivElement>(null);
  const [isFullscreen, setIsFullscreen] = useState(false);

  const toggleFullscreen = useCallback(() => {
    if (fullscreenRef.current) {
      const element = fullscreenRef.current as HTMLElement & {
        webkitRequestFullscreen?: () => Promise<void>;
      };

      const doc = document as Document & {
        webkitExitFullscreen?: () => Promise<void>;
      };

      if (!document.fullscreenElement) {
        if (element.requestFullscreen) {
          element.requestFullscreen();
        } else if (element.webkitRequestFullscreen) {
          element.webkitRequestFullscreen(); 
        }
      } else {
        if (doc.exitFullscreen) {
          doc.exitFullscreen();
        } else if (doc.webkitExitFullscreen) {
          doc.webkitExitFullscreen();
        } else if (doc.msExitFullscreen) {
          doc.msExitFullscreen();
        }
      }
    }
  }, []);

  useEffect(() => {
    const handleFullscreenChange = () => {
      setIsFullscreen(Boolean(document.fullscreenElement));
    };

    document.addEventListener("fullscreenchange", handleFullscreenChange);
    document.addEventListener("webkitfullscreenchange", handleFullscreenChange);

    return () => {
      document.removeEventListener("fullscreenchange", handleFullscreenChange);
      document.removeEventListener("webkitfullscreenchange", handleFullscreenChange);
    };
  }, []);

  return [fullscreenRef, toggleFullscreen, isFullscreen] as [
    React.RefObject<HTMLDivElement>,
    () => void,
    boolean
  ];
};

export default useFullscreen;

2. Tracking Fullscreen State

One of the challenges when working with fullscreen APIs is tracking whether the element is actually in fullscreen mode. The document.fullscreenElement property is our solution. This property returns the element currently being displayed in fullscreen mode, or null if no element is fullscreen.

To make sure the fullscreen state is always up-to-date, we add an event listener for the fullscreenchange event. This event fires whenever the document enters or exits fullscreen mode. Additionally, for compatibility with older browsers, we also listen to webkitfullscreenchange (Safari) and msfullscreenchange (Internet Explorer).

Inside the useEffect hook, we listen for these events and update the state accordingly:

TypeScript
useEffect(() => {
  const handleFullscreenChange = () => {
    setIsFullscreen(Boolean(document.fullscreenElement));
  };

  document.addEventListener("fullscreenchange", handleFullscreenChange);
  document.addEventListener("webkitfullscreenchange", handleFullscreenChange);

  return () => {
    document.removeEventListener("fullscreenchange", handleFullscreenChange);
    document.removeEventListener("webkitfullscreenchange", handleFullscreenChange);
  };
}, []);

This ensures that the isFullscreen state is updated whenever the user enters or exits fullscreen mode, allowing us to control UI elements based on the current fullscreen status.

3. Handling Browser Compatibility

Different browsers implement the Fullscreen API with slight variations. Safari, for example, uses the webkitRequestFullscreen method. Similarly, exiting fullscreen also requires different methods (webkitExitFullscreen).

In the toggleFullscreen function, we first check if the browser supports the standard requestFullscreen and exitFullscreen methods. If not, we fall back to the vendor-prefixed methods for Safari and Internet Explorer:

TypeScript
const element = fullscreenRef.current as HTMLElement & {
  webkitRequestFullscreen?: () => Promise<void>;
  msRequestFullscreen?: () => Promise<void>;
};

const doc = document as Document & {
  webkitExitFullscreen?: () => Promise<void>;
  msExitFullscreen?: () => Promise<void>;
};

if (!document.fullscreenElement) {
  if (element.requestFullscreen) {
    element.requestFullscreen();
  } else if (element.webkitRequestFullscreen) {
    element.webkitRequestFullscreen(); 
  } else if (element.msRequestFullscreen) {
    element.msRequestFullscreen(); 
  }
} else {
  if (doc.exitFullscreen) {
    doc.exitFullscreen();
  } else if (doc.webkitExitFullscreen) {
    doc.webkitExitFullscreen();
  } else if (doc.msExitFullscreen) {
    doc.msExitFullscreen();
  }
}

By checking for these methods before calling them, we ensure compatibility across most modern browsers while maintaining clean and reusable logic.

4. Reusing the Hook in Components

Now that we’ve built the hook, let’s see how we can use it in a component. By simply calling useFullscreen, we can enable fullscreen functionality on any div, video, or other elements.

Here’s an example of how to use it:

TypeScript
import React from "react";
import useFullscreen from "./useFullscreen";

const FullscreenDiv = () => {
  const [fullscreenRef, toggleFullscreen, isFullscreen] = useFullscreen();

  return (
    <div>
      <div
        ref={fullscreenRef}
        style={{
          width: "300px",
          height: "200px",
          backgroundColor: isFullscreen ? "lightblue" : "lightgreen",
          textAlign: "center",
          lineHeight: "200px",
          margin: "20px auto",
        }}
      >
        {isFullscreen ? "You are in fullscreen mode!" : "This is the div"}
      </div>
      <button onClick={toggleFullscreen}>
        {isFullscreen ? "Exit Fullscreen" : "Go Fullscreen"}
      </button>
    </div>
  );
};

export default FullscreenDiv;

This simple component demonstrates how easy it is to toggle fullscreen mode and reflect the current state using the useFullscreen hook. The button text and styles change based on whether the component is in fullscreen mode or not.

Conclusion

Building a reusable useFullscreen hook in React not only simplifies your codebase but also ensures you have a consistent way to manage fullscreen functionality across multiple components. By tracking fullscreen state, handling browser compatibility, and abstracting logic into a hook, you can create a more maintainable and robust application.

I hope this walkthrough has given you a clear understanding of how to build a fullscreen hook in React with TypeScript. This solution is scalable, reusable, and easy to integrate, making it a great addition to any project that requires fullscreen capabilities.

Share this:

Leave a Reply