Take the Responsivebility
Tags: react, css, webdev, state
Published: 2019-08-10 06:00:34

---
title: Take the Responsivebility
published: true
description: Be Responsible for Responsive. An Application State, which depends on the Media Target is still State.
tags: react, css, webdev, state
cover_image: https://thepracticaldev.s3.amazonaws.com/i/ba3jwnzsjea22khfylsq.jpg
---

- Mobile. Tablet. Desktop.
- Portrait. Landscape.
- NightMode on. And Off.
- Adaptive. Responsive.
- Responsible.

It's Saturday. Beautiful shiny and a bit windy morning. And I have to write this article, as long as I had the power to do it, and I have responsibility.

> Let's dive deeper into react media queries and different ways to use them as well as base application logic to match the current state of a device, where you applicating has been launched. That's not so easy. You cannot just `useMediaQuery` - query is just a question, not the asnwer.

# @media

I am not sure are you familiar with [CSS @media Rule](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries), so I would briefly explain what it is.

> Media queries are useful when you want to __modify your site or app depending on a device's general type__ (such as print vs. screen) or specific characteristics and parameters (such as screen resolution or browser viewport width).

Media queries might depend on: screen size, pixel density, screen type, pointer device used(mouse/touch), user settings(like reduced colors or motions), and device aspect ratio. And this is mainly CSS stuff.

# Colocation

In terms of CSS there are two common approaches to handle different media targets.

- separation
- colocation

### Per-Media Separation

With separation, you shall first __define all styles__ for your `main target`, and then _media-per-media_ override these values. Sometimes, when you don't want to override values (_don’t undo, just do_ principle) - you may even skip the first part.

> With mobile-first approaches, it's easy to make a layout work well on narrow screens, and then "undo" most of
it on desktop. But that's tricky because you have to keep track of what has been done outside of media queries,
and reset those values inside the desktop media query. You also end up writing a lot of CSS just to reset
values, and you can end up leaving CSS like margin-bottom: 0 you are not sure what.
The margin-bottom set for the .side element should only appear on mobile.
Instead of applying a margin by default on all screens, and removing it on desktop, we only apply it on mobile. [Jeremy Thomas, CSS in 44 minutes](https://jgthms.com/css-in-44-minutes-ebook/)

__Rules are colocated on per media basis__.

```sass
.box {
  position: absolute;
  left: 0;
  top: 0;
}

.bar {
  position: sticky;
  top: 0;
}

.button {
  composes: flex-center;
}

@media screen and (min-width: $tablet) {
  .box {
    top: 1rem;
  }

  .bar {
    justify-content: center;
    width: $pin-size;
  }
}

@media screen and (min-width: $desktop) {
  .box {
    top: 0rem;
  }

  .button {
   composes: flex-right;
  }
}
```

- 👍 - easy to understand how one screen differs from another
- 👎 - harder to change styles, easy to forgot update media override.



### Per-Rule Separation
In per-rule separation, everything related to one rule should be defined in __one rule__.

__Rules are colocated on per selector basis__.

```sass
.box {
  position: absolute;
  left: 0;
  top: 0;

  @media screen and (min-width: $tablet) {
    & {
      top: 1rem;
    }
  }
 
  @media screen and (min-width: $desktop) {
    & {
      top: 0rem;
    }
  }
}

.bar {
  position: sticky;
  top: 0;

  @media screen and (min-width: $tablet) {
    & {
      justify-content: center;
      width: $pin-size;
    }
  }
}

.button {
  composes: flex-center;

  @media screen and (min-width: $desktop) {
    .button {
      composes: flex-right;
    }
  }
}
```

File with this pattern applied might look larger, however, after CSSO(or any other CSS optimiser) it would be _reformatted_ to a __per-media__ format.

- 👍 - easy to change styles, move styles and share styles.
- 👎 - harder to understand how one screen different from another

The choice is quite personal here, but with a component approach (in your mind) the second way is better. Especially from a maintenance point of view.

Colocation, coherence, distance, cognitive load and pattern matching are very big things for CSS world. Wanna know more?

{% link https://dev.to/thekashey/happy-potter-and-the-order-of-css-5ec %}

Anyway - it's not a big deal to handle this from CSS point of view, and __please always do it__, if it's possible. SSR would say thank you, as well as browser. But that's about React?

# HTML/React

And there is a big problem with React, mostly from [The Great Divide](https://css-tricks.com/the-great-divide/) - React developers are not (and not required) fluent with CSS. So they invented their own ways.

Let's go thought most popular ways:

### 1👑 React-responsive

[React-responsive](https://github.com/contra/react-responsive) is actually very old repo, and was changing a bit over time. Today it has `hooks` and `Components` API

```js
const isDesktopOrLaptop = useMediaQuery({
  query: '(min-device-width: 1224px)'
})
const isBigScreen = useMediaQuery({ query: '(min-device-width: 1824px)' })

<MediaQuery minDeviceWidth={1224} device={{ deviceWidth: 1600 }}>
   <p>You are a desktop or laptop</p>
   <MediaQuery minDeviceWidth={1824}>
     <p>You also have a huge screen</p>
   </MediaQuery>
</MediaQuery>
```

Each component, or hook, is a __single__ operation, and does not have an `else` case.



### 2👑 React-Media
[React-Media](https://github.com/ReactTraining/react-media) from `ReactTraining` - takes the second place.

Component API is the main API
```js
<Media query="(max-width: 599px)">
   {matches =>
     matches 
       ? <p>The document is less than 600px wide.</p>
       : <p>The document is at least 600px wide.</p>
   }
</Media>
```

Again - each component is a __single__ operation, but this time does have an `else` case.

### 3👑 Third places

Third place would be separated between 3 libraries. They all a FAR less popular than winners, but still - quite popular.

### 3/1 react-responsive-mixin

And the first is very strange (yet popular) [react-responsive-mixin](https://github.com/akiran/react-responsive-mixin). Which is even not React 15 compatible. It's really that old `createClass` mixin, but still popular, and still works

```
var Component = React.createClass({
  mixins: [ResponsiveMixin],
  getInitialState: function () {
    return { url: '/img/large.img' };
  },
  componentDidMount: function () {
    this.media({maxWidth: 600}, function () {
      this.setState({url: '/img/small.jpg'});
    }.bind(this));
  },
  render: function () {
    return <img src={this.state.url} />;
  }
});
```

### 3/2 react-sizes

Second or Thirds - [react-sizes](https://github.com/renatorib/react-sizes). A redux-like query "state" manager

```js
const mapSizesToProps = ({ width }) => ({
  isMobile: width < 480,
})

export default withSizes(mapSizesToProps)(MyComponent);
```


### 3/3 use-media

And the last is [use-media](https://github.com/streamich/use-media), which is basically no more than a `hook`

```js
const Demo = () => {
  // Accepts an object of features to test
  const isWide = useMediaLayout({minWidth: 1000});
  // Or a regular media query string
  const reduceMotion = useMediaLayout('(prefers-reduced-motion: reduce)');

  return (
    <div>
      Screen is wide: {isWide ? '😃' : '😢'}
    </div>
  );
};
```

# The competition is over, all fired 🔥

All libraries above are doing something very very wrong. Matching queries is not what you need.

Again what is media query:
> Media queries are useful when you want to __modify your site or app__ depending on a device's

And what does mean __modify__, and how "grouping" in __terms of CSS__ could help you here?

Probably, let's double check how the problem could be solved from HTML way. And by HTML way I've assumed Bootstrap! [bootstrap's responsibility utilities](https://v4-alpha.getbootstrap.com/layout/responsive-utilities/).

> - The .hidden-*-up classes hide the element when the viewport is at the given breakpoint or wider. For example, .hidden-md-up hides an element on medium, large, and extra-large viewports.
> - The .hidden-*-down classes hide the element when the viewport is at the given breakpoint or smaller. For example, .hidden-md-down hides an element on extra-small, small, and medium viewports.

This works roughly the same for any "atomic" CSS framework - just define a few classes and call it a day

```html
// tailwind
<!-- Width of 16 by default, 32 on medium screens, and 48 on large screens -->
<img class="w-16 md:w-32 lg:w-48" src="...">

// bootstrap
<div class="col-md-6 col-lg-4 col-xl-3">
   <h2>HTML</h2>
</div>

// bulma
<div class="column is-half-mobile  is-one-quarter-tablet></div>
```

What is crucial here - __everything is defined in one place__. One `class` defines how component should work in all variances of target devices.

Let's think about this.

# Finite State Machines

Yes! We started from CSS and here __Parallel Finite State Machines__ comes to play.

> Finite State Machine could be in only one state at one point of time. As long as __state__ for React users might mean something different( like `state`!) let's use another term - a `Phase`.

- Could water be hot and cold. Yes, but not simultaneously.
- Could display be wide and small? Yes, but not simultaneously.
- Could orientation be different? Yes, but not simultaneously.

Device is represented by many different states, some simple boolean, but some are more complex, which coexist simultaneously.

You may use "single query" to handle "boolean" states, but could not use it for states like screen size, as long as it could be up 5(easy!) different targets. And that's mean that you have to __make up to 5 decisions simultaneously__. Ok, let's do just 3.

```js
<MediaMatcher
    mobile={"render for mobile"}
    tablet={"render for tablet"}
    desktop={"render desktop"}
/>

const title = useMedia({
  mobile: "Hello",
  tablet: "Hello my friend",
  desktop: "Hello my dear friend, long time no see",
});
```

This is the same sort of _colocation_ we have with the "Per-Rule Separation", and the same sort we might have with "Atomic CSS".

Probably, some additional CSS-world patterns should be applicable:
```js
// would render "render for mobile" for the "missed" tablet
<MediaMatcher
    mobile={"render for mobile"}
    // tablet={"render for tablet"}
    desktop={"render desktop"}
/>

// desktop would inherit "Hello my friend" from tablet
const title = useMedia({
  mobile: "Hello",
  tablet: "Hello my friend",
  // desktop: "Hello my dear friend, long time no see",
});
```

The rule is simple - __pick the value to the left__, also known as a __mobile-first__. And there is no way you might "forget" to provide some value for a specific target, especially if that target did not exists yesterday (you know, designers love to change their mind).

> Media matching should not be just __conditions__ in JavaScript. It shall be __decisions__, __forks__, __switches__.

Existing "media matches" gives your ability to _match_ one `media query`, and some of them let do something in the `else` branch. 

As I mention above - size based media queries might require more than two decisions made in one place. However - there is a subset of media matching, where logic is still __binary__, regardless how many `phases` you have in real. It's `Above`, and `Below`. Or `Display-only` and `Dont-display`.

Some libraries, like [smooth-ui](https://smooth-ui.smooth-code.com/docs-utilities-breakpoint) has `Up` and `Down` __breakpoints__, and with combination they are letting you to match any __intervals__ you might need.

I hope the same "intervals" are doable with the approach I am talking about.
```js
// dont display on mobile
<MediaMatcher
    mobile={null}
    tablet={"something to render above mobile"}
/>

// dont display on mobile
<MediaMatcher
    mobile={"something to render only `till tablet`(on mobile)"}
    tablet={null}
/>
```

You also might create _alternative_ states, for the extra stuff media queries might give you:

```js
const HoverMedia = createMediaMatcher({
  mouseDevice: "(hover: hover)",
  touchDevice: "(hover: none)",
});
// here is a trick - the order matters.
// we are putting a server("false") branch last, 
// so it would be a nearest "value to the left"

const MyComponent = () => {
  const autoFocus = HoverMedia.useMedia({
    touchDevice: false,
    mouseDevice: true,
  });
  
  // do not autofocus inputs on mobile
  // to prevent Virtual Keyboard opening
  return <input autoFocus={autoFocus}/>
}
```

Even ClientSide and ServerSide stuff could be managed this way. There are components which shall not be ServerSide rendered, or could be rendered on the client in "other way".

```js
const SideMedia = createMediaMatcher({
  client: false,
  server: true,
});

// you can flip Server/Client only after "hydration" pass
// or SSR-ed HTML could not match CSR-ed one.
const SideMediaProvider = ({children}) => {
  const [isClient, setClient] = useState(false);
  useEffect(() => setClient(true), []);

  return (
    <SideMedia.Mock client={isClient}>
     {children}
    </SideMedia.Mock>
  )
}

const DisplayOnlyOnClient = ({children}) => {
  const display = SideMedia.pickMatch({
    client: true,
    server: false,
  });

  return display ? children : null;
}
```

And there is one more thing - all examples above were from an existing library, which, although, is faaar from being popular.

{% github theArnica/react-media-match %}

We created `react-media-match` to handle __device state__ as application state, not to _match random queries_. We tackle responsibility problem quite responsible.

### A note about performance
In short, there are two approaches to the API 
- most common is when you might specify `media-query`, or handle resize in any other way, in the place of use.
- less common is using one central store, and all `redux` based media libraries like [redux-mediaquery](https://github.com/Yaska/redux-mediaquery) are doing it.

For example, that's how `react-responsive` and `use-media` are working:
```js
useMediaQuery({ query: '(min-device-width: 1824px)' })
```

`react-media` is absolutely the same, even if it's component based. Your ability to provide any query to the component means that every components hold it's own subscription.
```js
<Media query="(max-width: 599px)">
```

`react-sizes`? Every `withSizes` attach `onResize` listener to the window and act independently, even if all subscriptions are doing the same thing. Bonus - `react-sizes` also __throttles__ the resize callback.
That's a __mistake__. To be more correct - all libraries are doing something wrong.

#### What's wrong with it?
It's a long story, but React works a bit differently for updates caused by _React controlled code_(event handlers, lifecycle methods, etc), and the __non-controlled__ ones.

When you call `setState` inside life cycle event - it does nothing, but schedules update, which, among with other scheduled updates, would be executed after your code would return _execution_ back to React.

However, you can't use React to add `resize` event listener to the `window`, as well as match `query`. Thus these callbacks would be executed in a _React uncontrolled_ way, and in this case `setState` would be synchronous. Causing as much cascade tree updates, as much _connected_ components you have.

There are only two ways to handle this:
- use a single source of truth, like `redux` or some `Context` based state, sitting on top of your app.
- use yet undocumented, but stable and adopted by many libraries `ReactDOM.unstable_batchedUpdates` to combine all updates in one..

{% twitter 1103399900371447819 %}

> Fun fact - many libraries implement callback throttling, but they are making the ​performance even worse.

***

With great power comes great responsibility. You shall be more responsible for the responsive side of your application, and think about media queries as you think about __state__. 

__Take the Responsivebility__.

> PS: if you are not quite sure why do to need "responsive" on React side - here is [another article](https://medium.com/@antonkorzunov/adaptive-responsive-reactive-62fb938d6191).