DEV Community

Cover image for React: Custom hook for media queries 📱💻
Andrew Bone
Andrew Bone

Posted on

React: Custom hook for media queries 📱💻

Today we'll be making a React hook that will take a media query and will return whether that query resolves to true or false.

What I wanted to achieve

The aim here is to have a state that will update when media changes. This will mostly be for screen sizes, allowing us to do some great responsive stuff, but will also work with any media query.

Media Queries

You maybe thinking media queries are *only for screen size or even what even are media queries? So let's have a quick look.

If we head over to MDN, and I suggests that's the always the place to start, we see a whole list of things that we can use from aspect-ratio to orientation and even resolution.

/* Minimum aspect ratio */
@media (min-aspect-ratio: 8/5) {
  div {
    background: #9af; /* blue */
  }
}

/* Maximum aspect ratio */
@media (max-aspect-ratio: 3/2) {
  div {
    background: #9ff;  /* cyan */
  }
}

/* Exact aspect ratio, put it at the bottom to avoid override*/
@media (aspect-ratio: 1/1) {
  div {
    background: #f9a; /* red */
  }
}
Enter fullscreen mode Exit fullscreen mode

You can look down the list over on MDN and see what all the different properties do, I'm going to focus and 3 here that I feel are important and are often overlooked.

prefers-color-scheme

The prefers-color-scheme CSS media feature is used to detect if the user has requested a light or dark color theme.

This means we can change the whole look and feel of our sites to match the theme a user is requesting, this is not only be for ascetics but if a user has sensitive eyes they may request dark mode by default and we can deliver that making a better, more seamless, user experience.

prefers-contrast

The prefers-contrast CSS media feature is used to detect if the user has requested that the web content is presented with a higher (or lower) contrast.

This one currently only works in safari but support will come and it doesn't hurt to have your apps ready to hit the ground running. This is another feature with huge accessibility implications. If someone can't read your text because they need a higher contrast then your site is no good to them.

prefers-reduced-motion

The prefers-reduced-motion CSS media feature is used to detect if the user has requested that the system minimize the amount of non-essential motion it uses.

Again accessibility related, are you noticing a pattern? Animations are cool, I love adding animations to my sites and apps but some people get motion sickness from them. With this we can simple change animation to fade-ins rather than slides, or even remove the animations completely if you want to.

The hook

The hook is going to be quite a simple one it's only going to use useState and useEffect as well as matchMedia. I'll leave you to read through the code then I'll talk a little about the try - catch bit after.

export default function useMediaQuery(initalQuery: string) {
  const [query, setQuery] = useState(initalQuery);
  const [matches, setMatches] = useState(false);

  // check query and listen for media change.
  useEffect(() => {
    if (!query) return;

    const _onChange = (mql: MediaQueryListEvent) => {
      setMatches(mql.matches);
    };

    const mql = window.matchMedia(query);

    setMatches(mql.matches);

    try {
      mql.addEventListener("change", _onChange);
    } catch {
      mql.addListener(_onChange);
    }

    return () => {
      try {
        mql.removeEventListener("change", _onChange);
      } catch {
        mql.removeListener(_onChange);
      }
    };
  }, [query]);

  return [matches, setQuery] as const;
}
Enter fullscreen mode Exit fullscreen mode

So that try - catch when the spec was originally made addListener(func) was the way to listen for media change but in 2015, 2 years after iE11 the spec was changes to standardise this with other event listeners and thus addEventListener('change, func) was born. First we try the new way and if it fails we do it the old way this means in all modern browsers we get the new way straight away and in old browser, IE11, we wait a couple of milliseconds longer before adding the listeners.

Examples

Below are a couple of examples, feel free to look through the code and ask any questions you might have.

Fin

And there we have it, a React hook that will return the result of a media query into a state and will update that state when, and if, the media changes. This is such a useful hook to have in your belt and a nice simple one to get started learning how to write them.

As always if you have any questions or think I've done anything wrong please don't hesitate to leave a comment. Thank you all for reading this far.

Refs

Discussion (3)

Collapse
link2twenty profile image
Andrew Bone Author

Of course I've written this article with responsive and accessibility in mind. If you have any cool examples, showing off these or anything else, feel free to link to them in the comments. It's always great to see what people are making.

Collapse
httpjunkie profile image
Eric Bishard

I like the tutorial, I'm already using the react-media-hook and it's useMediaPredicate() hook. Have you used that one?

Collapse
link2twenty profile image
Andrew Bone Author

I've not used it but I've just had a look at the code. useMediaPredicate() doesn't update itself, which maybe you would want but it's not something I've added to this one too. useMedia() does the same as this one, without an option to change the query, but uses react useCallback so will use a tiny bit more memory but that's alright.

There's nothing wrong with using something someone else has written but it's good to understand how things work because one day they'll be a hook you need that no-one has made yet. 😅