DEV Community

Juhana Jauhiainen
Juhana Jauhiainen

Posted on • Originally published at juhanajauhiainen.com

How to implement useMediaQuery hook in React

What are Media Queries?

Media Queries are a CSS feature that can be used to conditionally apply selected styles on an HTML element. Some examples of media queries include checking for the width of the browser window, checking for the media type (print, screen), or checking for dark/light mode preference.

The most common use case for media queries is using it to implement responsivity on a website. Checking for the width of the viewport and applying styles based on it allows us to define different styles on different devices (desktop, mobile, tablet).

The syntax for media queries is comprised of an optional media type and any number of media feature expressions. Media types include all,screen, and print. The default value for media type is all.

.header {
  font-size: 20rem;
}

@media print {
  .header {
    font-size: 15rem;
  }
}
Enter fullscreen mode Exit fullscreen mode

The media type is followed by any number of media feature expressions enclosed in parenthesis.

.header {
  font-size: 20rem;
  color: pink;
}

@media (max-width: 800px) {
  .header {
    color: blue;
  }
}
Enter fullscreen mode Exit fullscreen mode

Here, max-width: 800px is a media feature expression which signifies that this CSS will only be applied if the width of the viewport is equal to or less than 800 pixels.

There is a number of different media features we can use to apply CSS in specific situations. The most common is width because it is used when creating responsive and mobile-friendly websites.

How to use Media Queries from JavaScript?

If you want to check for a media query using JavaScript, you can use the window.matchMedia function. matchMedia takes a single parameter, the query string you want to check for, and returns a MediaQueryList object. The MediaQuery object can be used to check for a match or to attach a change event listener. The change listener is called every time the result of the media query changes.

const result = window.matchMedia("(max-width: 800px)");
if (result.matches) {
  // do something
}

result.addEventListener("change", (event) => {
  if (event.matches) {
    // do something
  }
});
Enter fullscreen mode Exit fullscreen mode

Custom React hook for Media Queries

matcMedia makes it possible to implement a React Hook we can use to check for Media Query matches and change your application UI or behavior based on the results.

First, let's define the API for our hook. With TypeScript, our hooks type definition would look like this.

type useMediaQuery = (query: string) => boolean;
Enter fullscreen mode Exit fullscreen mode

Our hook will take the query string as a parameter and return a boolean. Next, we'll need to add a React.useEffect call which calls matchMedia and adds an event listener for change. We also need a state variable to store the match.

function useMediaQuery(query) {
  const [matches, setMatches] = React.useState(false);
  React.useEffect(() => {
    const matchQueryList = window.matchMedia(query);
    function handleChange(e) {
      setMatches(e.matches);
    }
    matchQueryList.addEventListener("change", handleChange);
  }, [query]);

  return matches;
}
Enter fullscreen mode Exit fullscreen mode

Great 🥳 This already works but we have one more important thing to add.. cleanup for the event handler. React.useEffect function can return a function used for cleanup. It's commonly used to unregister event handlers or unsubscribing from external data sources.

Let's add a cleanup function to our useEffect

function useMediaQuery(query) {
  const [matches, setMatches] = React.useState(false);

  React.useEffect(() => {
    const matchQueryList = window.matchMedia(query);
    function handleChange(e) {
      setMatches(e.matches);
    }
    matchQueryList.addEventListener("change", handleChange);

    return () => {
      matchQueryList.removeEventListener("change", handleChange);
    };
  }, [query]);

  return matches;
}
Enter fullscreen mode Exit fullscreen mode

Now our useMediaQuery hook is finished. Here's how you would use it.

function SomeComponent() {
  const isMobile = useMediaQuery("min-width: 768px)");

  return <h1>Browsing with {isMobile ? "phone" : "desktop"}</h1>;
}
Enter fullscreen mode Exit fullscreen mode

Links

MDN on window.matchMedia
MDN on MediaQueryList

Discussion (1)

Collapse
lukeshiru profile image
Luke Shiru

You can make it so query is an object, making the API a little more expressive:

import { useState, useEffect, useCallback, useMemo } from "react";

const parseMediaQuery = queryObject =>
    Object.entries(queryObject)
        .map(([query, value]) => `(${query}: ${value})`)
        .join(" and ");

const useMediaQuery = queryObject => {
    const [matches, setMatches] = useState(false);
    const matchQueryList = useMemo(
        () => globalThis.matchMedia(parseMediaQuery(queryObject)),
        [queryObject],
    );
    const mediaQueryListChange = useCallback(
        ({ matches }) => setMatches(matches),
        [matchQueryList],
    );
    useEffect(() => {
        matchQueryList.addEventListener("change", mediaQueryListChange);

        return () =>
            matchQueryList.removeEventListener("change", mediaQueryListChange);
    }, [mediaQueryListChange]);

    return matches;
};

// And then...

const mobileQuery = {
    "min-width": "768px",
};

const SomeComponent = () => {
    const isMobile = useMediaQuery(mobileQuery);

    return <h1>Browsing with {isMobile ? "phone" : "desktop"}</h1>;
};
Enter fullscreen mode Exit fullscreen mode

Worth mentioning that even if is good for an example, we shouldn't design UIs and behaviors based on if "is mobile", and instead make it responsive so it looks ok in any size, no matter the device. In short: feature based instead of device based.

Cheers!