React: Custom hook for media queries 📱💻
Tags: react, tutorial, typescript
Published: 2021-08-07 10:30:34

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](https://developer.mozilla.org/en-US/docs/Web/CSS/@media), 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`. 

```css
/* 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 */
  }
}
```

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`](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia). I'll leave you to read through the code then I'll talk a little about the `try` - `catch` bit after.

```tsx
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;
}
```
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.

{% codesandbox elastic-tdd-c85hr %}

## 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
- [https://css-tricks.com/touch-devices-not-judged-size/](https://css-tricks.com/touch-devices-not-judged-size/)
- [https://developer.mozilla.org/en-US/docs/Web/CSS/@media](https://developer.mozilla.org/en-US/docs/Web/CSS/@media)
- [https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia)