Build a custom media query composable for Vue apps
Tags: javascript, vue
Published: 2021-10-17 21:51:17

### Introduction

Let's face it, building a fully responsive website can be a daunting task and while the functionality CSS provides are usually enough to make your site adapt to the various screen sizes, there are times when you need a little JavaScript to get it just right and nail that smooth user experience on every device.

Imagine for a second, you're building a dashboard for some e-commerce store to manage all sorts of data and you have a sidebar for navigating the various menus. Naturally the sidebar is the less important component of the page so when you want to adjust for smaller screen sizes, this is the component that gives way. We can hide the sidebar completely on mobile and even show a different variant of the sidebar on tablets.

JavaScript media queries offer the flexibility to conditionally render components which brings performance benefits because you don't have to render elements that are not visible to the user.

### The Composable

Composables in Vue 3 are like hooks in react. They provide an intuitive way to extract reactive state and functionality to separate modules or as you will, **composables**.

> Note: The code here leverages features unique to Vue 3 as it makes use of the composition API.

We're going to build a media query composable that accepts a media query string and essentially listens for changes in the window size and returns a variable that tells us if the current window size matches the query string.

```js
// useMedia.js
import { ref, watchEffect } from "vue";

export const useMedia = (query) => {
   const matches = ref(true);

   watchEffect((onInvalidate) => {
      const media = window.matchMedia(query);

      if(media.matches !== matches) {
         matches.value = media.matches;
      }

      const onChange = () => {
         matches.value = media.matches;
      }

      media.addEventListener("change", onChange);

      onInvalidate(() => {
         media.removeEventListener("change", onChange);
      });
   });

   return matches
}
```

#### Usage
```js
import { useMedia } from "../../composables/useMedia";
...
const isMobile = useMedia("(max-width: 425px)")
...
```

In the snippet above we use the `watchEffect` function to subscribe to changes in the `matches` variable. In the watchEffect callback, we are listening for a change event on the `matchMedia` property of the window object. When a change is detected, it calls the `onChange` function which will update the `matches` reactive object.

We also have an `onInvalidate` function being called, which receives a callback function where we remove the event listener. This is essentially a **clean-up** function like you'd have in a react `useEffect` hook. This lets us cancel all subscriptions when the component is unmounted.

This is how we can easily build our own custom re-usable function to use JavaScript media queries in our components. If you have any questions or thoughts on this, drop a comment below.

#### References
[JavaScript Media Queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries#media_queries_in_javascript)
[Composables](https://vueschool.io/articles/vuejs-tutorials/what-is-a-vue-js-composable/)
[Side Effect Invalidation](https://v3.vuejs.org/guide/reactivity-computed-watchers.html#side-effect-invalidation)