React. Responsiveness made easy
Tags: react, frontend, performance, css
Published: 2023-03-07 07:53:19

### The problem

Sometimes you want the current responsiveness interval in JavaScript.

Most attempts (and answers on StackOverflow) are placing an event listener on `window.resize` and then use some custom logic (a `switch` statement) to output the current interval.

Works, but not ideal.

Biggest problem is it runs on `window.resize`, which fires tens of times per second  while resizing, creating tiny freezes in page display, a clear sign the browser is struggling
___
### The solution

A cleaner (and more performant) alternative is to use [`window.matchMedia(queryString)`](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia). 

It returns an instance of [`MediaQueryList`](https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList).

Binding a callback on this instance's `change` event ensures the callback will only be called when the media query starts/stops matching.
```ts
const [isSm, setIsSm] = useState(false)
useEffect(() => {
  const queryList = window.matchMedia(
    '(min-width: 640px) and (max-width: 767.9px)'
  )
  queryList.addEventListener('change', ({ matches }) =>
    setIsSm(matches)
  )
}, [])
```
The other cool thing about it is you don't need to clean up the bound event. If you delete the `MediaQueryList` itself, the listener is garbage collected, so no listeners are left behind on `<body>` or `window` object after the component unmounted.
___
### Wrap up

Having seen this question answered less than ideal a few times, I decided to write a tiny plugin ([react-responsiveness](https://www.npmjs.com/package/react-responsiveness)) which takes an optional config object (`interval: min` dictionary - defaults to Bootstrap 5's breakpoints) and returns a context provider and a hook.

I added presets for commonly used frameworks: `Bootstrap_3`, `Bootstrap_4`, `Bootstrap_5`, `Bulma`, `Chakra`, `Foundation`, `Ionic`, `Material_Design`, `Materialize`, `Material_UI`, `Quasar`, `Semantic_UI`, `Skeleton`, `Tailwind_CSS`, `Vuetify`, `Windi_CSS`.
___
#### Installation
```terminal
yarn add react-responsiveness
```

#### Add the provider

```ts
import {
  ResponsivenessProvider,
  Presets,
} from "react-responsiveness";

function App() {
  // ...
}

const WithResponsiveness = () => (
  <ResponsivenessProvider breakpoints={Presets.Tailwind_CSS}>
    <App />
  </ResponsivenessProvider>
);
export default WithResponsiveness;
```
___
#### Use in components

The hook exposes `isMin`, `isMax` and `isOnly` helper methods, the `currentInterval` and a more verbose `matches` object, which can be all be used to show/hide things responsively:

```tsx
import { useResponsiveness } from "react-responsiveness"

const { isMin, isMax, isOnly, currentInterval } =
  useResponsiveness()

return (
  <>
    <div>Current interval {currentInterval}</div>
    {isMin("md") && (
      // @media (min-width: 768px)
      <div>content...</div>
    )}
    {isMax("md") && (
      // @media (max-width: 991.9px)
      <div>content...</div>
    )}
    {isOnly("md") && (
      // @media (min-width: 768px) and (max-width: 991.9px)
      <div>content...</div>
    )}
  </>
)
```
**Note**: the interval names vary based on the preset or config you used. 
___
#### Use with bespoke breakpoints
```tsx
<ResponsivenessProvider
  breakpoints={{
    small: 0,
    medium: 777,
    large: 1234,
  }}
>
  // ...
</ResponsivenessProvider>
```

```tsx
{isOnly('medium') && (
  // @media (min-width: 777px) and (max-width: 1233.9px)
  <div>content...</div>
)}
```
___
#### Demo

https://codesandbox.io/p/github/codesandbox/codesandbox-template-vite-react/csb-ss87sf/react-responsiveness-v1?file=%2Fsrc%2FApp.tsx
___

If you find it useful, let me know. 

Cheers!