DEV Community

Teymur Salimzade
Teymur Salimzade

Posted on

Handle Media Query in React with hooks

Hi all, I recently had a need to process media query dynamically in a react application, so I wrote a hook to help me with that.

I'll describe a little bit of what it is and a plan of action.

The Purpose of useMediaQuery

The useMediaQuery hook is a custom React hook that allows developers to track the state of a CSS media query. The hook returns a boolean value indicating whether the document currently matches the media query string provided as an argument.

How It Works

Here’s a step-by-step explanation of the code:

  1. Import React Hooks: The useState and useEffect hooks are imported from React to manage state and side effects.
  2. Define the Hook: useMediaQuery is defined as a function that takes a query string as its parameter and returns a boolean.
  3. Initialize State: A state variable matches is initialized to false, representing whether the media query matches.
  4. Use Effect Hook: Inside the useEffect hook:
  • A MediaQueryList object media is created by calling window.matchMedia(query).

  • A conditional check updates the matches state if the current state doesn’t match the media.matches value.

  • A listener function is defined to update the matches state whenever the media query status changes.

  • The listener is added as an event listener to the media object for the ‘change’ event.

  • A cleanup function is returned to remove the event listener when the component unmounts or the query changes.

  1. Return Value: The hook returns the current value of matches.

The Code

mq.hook.tsx

import { useState, useEffect } from "react";

// Define the hook with 'query' parameter typed as a string
const useMediaQuery = (query: string): boolean => {
  const [matches, setMatches] = useState<boolean>(false);

  useEffect(() => {
    const media = window.matchMedia(query);
    if (media.matches !== matches) {
      setMatches(media.matches);
    }

    // Define the listener as a separate function to avoid recreating it on each render
    const listener = () => setMatches(media.matches);

    // Use 'change' instead of 'resize' for better performance
    media.addEventListener("change", listener); 

    // Cleanup function to remove the event listener
    return () => media.removeEventListener("change", listener);

  }, [matches, query]); // Only recreate the listener when 'matches' or 'query' changes

  return matches;
};

export default useMediaQuery;

Enter fullscreen mode Exit fullscreen mode

Usage

app.component.tsx

import { useMediaQuery } from "../hooks/mq.hook";

function App() {
    const isMobile = useMediaQuery("(max-width: 768px)");

    return (
        <div>
            {isMobile ? <h1>"Mobile"</h1> : <h1>"Desktop"</h1>}
        </div>
    )
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Summary

The useMediaQuery hook is a powerful tool for React developers to create dynamic and responsive applications. By leveraging TypeScript, the code ensures type safety, enhancing the development experience and reducing runtime errors. The hook’s design follows best practices, such as defining the listener outside the effect to avoid unnecessary re-creations and using the ‘change’ event for optimal performance.

This hook can be easily integrated into any React project to provide real-time responsiveness based on any media query, making it an essential utility in the responsive design toolkit.

Top comments (1)

Collapse
 
kwfjm profile image
Masashi Kawafuji • Edited

Since React 18, we can also use useSyncExternalStore hook provided by React!

Here is an example bellow.

import { useRef, useSyncExternalStore } from "react";

export default function useMediaQuery(query: string) {
  const mediaQuery = useRef(window.matchMedia(query));

  return useSyncExternalStore(
    (callback) => {
      mediaQuery.current.addEventListener("change", callback);
      return () => {
        mediaQuery.current.removeEventListener("change", callback);
      };
    },
    () => mediaQuery.current.matches,
  );
}
Enter fullscreen mode Exit fullscreen mode