react-media-query-hoc
Version:
A dead simple React Higher Order Component (HOC) that uses context for matching media queries
193 lines (142 loc) • 6.75 kB
Markdown
# react-media-query-hoc [](https://travis-ci.com/DomainGroupOSS/react-media-query-hoc) 
A dead simple React Higher Order Component (HOC) that uses context for matching media queries.
## Why use this?
- A simple API which doesnt require you to put `MediaQuery` components all over your code base
- More performant (you only need 1 parent `MediaQueryProvider` that listens to media events you wish to configure)
- Easier to test than other react media query libraries
- Small [bundlephobia](https://bundlephobia.com/result?p=react-media-query-hoc@latest)
- Uses [css-mediaquery](https://github.com/ericf/css-mediaquery) which parses and determines if a given CSS Media Query
matches a set of values (used for server side rendering).
- You want specific react components being mounted and rendered based on media types
## Why not use this?
We generally recommend using vanilla CSS media queries to build responsive websites, this is simpler, provides a smoother UX, also it mitigates having to guess the screen width during [server side rendering](#server-side-rendering).
Use this library if you need to dramatically alter the page layout between media types (**some examples include:** an experiment needs to be run on a specific screen width or an advertisement needs to be on specific screen width).
## Install
Via [NPM](https://docs.npmjs.com/):
```bash
npm install react-media-query-hoc --save
```
Via [Yarn](https://yarnpkg.com/en/):
```bash
yarn add react-media-query-hoc
```
## Usage
This library is designed so that you have 1 `MediaQueryProvider` parent and 1-many child components wrapped with `withMedia` HOC
### `MediaQueryProvider`
This component will listen to media events you want to configure, it should be used once as a parent component.
**Usage:**
```javascript
import { MediaQueryProvider } from 'react-media-query-hoc';
const App = (props) => {
return (
<MediaQueryProvider>
<TheRestOfMyApp />
</MediaQueryProvider>
);
};
export default App;
```
By providing no `queries` prop to the `MediaQueryProvider` component, it will default to these [media queries](https://github.com/jooj123/react-media-query-hoc/blob/8d1a3860dc29462436ca9545a33904cb0d38afae/src/media-query-provider.js#L5)
But you can provide different media queries for your use case using the `queries` prop, eg:
```javascript
const App = (props) => {
const customQueries = {
verySmall: 'screen and (max-width: 300px)',
someOtherMediaQuery: 'screen and (min-width: 301px)',
};
return (
<MediaQueryProvider queries={customQueries}>
<TheRestOfMyApp />
</MediaQueryProvider>
);
};
```
### `withMedia`
This is a HOC to provide media match props to your component.
**Usage:**
```javascript
import { withMedia } from 'react-media-query-hoc';
const MyComponent = ({ media, ...props}) => {
if (media.tablet || media.mobile) {
return (
<div>
Mobile and Tablet View
</div>
)
}
return (
<div>
Other View
</div>
);
};
export const BaseMyComponent = MyComponent;
export default withMedia(MyComponent);
```
Components wrapped by `withMedia()` won't work with React's usual `ref` mechanism, because the ref supplied will be for `withMedia` rather than the wrapped component. Therefore a prop, `wrappedRef` provides the same function. Note: this means the wrapped component can not be a [stateless function](https://github.com/facebook/react/issues/10831).
### `MediaContext`
This is the React Context exported and ready to be used with React `useContext` hook. It has a default value of `{}`, present when the component that consumes the context is not wrapped with `MediaQueryProvider`.
```javascript
import { MediaContext } from 'react-media-query-hoc';
import { useContext } from 'react';
const MyComponent = (props) => {
const media = useContext(MediaContext);
if(media.tablet || media.mobile) {
return (
<div>
Mobile and Tablet View
</div>
)
}
return (
<div>
Other View
</div>
);
};
export default MyComponent;
// default value
ReactDOM.render(<MyComponent />); // Renders 'Other View';
```
### Server Side Rendering
You can pass in media features from your server, all supported values can be found [here](https://www.w3.org/TR/css3-mediaqueries/#media1).
**Usage (matches mobile screen during SSR):**
```javascript
const App = (props) => {
const values = {
width: 300,
type: 'screen',
};
return (
<MediaQueryProvider values={values}>
<TheRestOfMyApp />
</MediaQueryProvider>
);
};
```
#### React 16 ReactDOM.hydrate
It's very important to realise a server client mismatch is dangerous when using hydrate in React 16, ReactDOM.hydrate
can cause very [strange](https://github.com/facebook/react/issues/10591) html on the client if there is a mismatch.
To mitigate this we use the two-pass rendering technique mentioned in the React [docs](https://reactjs.org/docs/react-dom.html#hydrate).
We render on the client in the first pass using `values` with `css-mediaquery` used on the server, then we use the browsers native `window.matchMedia`
to get it's actual dimensions and render again if it causes different query results. This means there should be no React
server/client mismatch warning in your console and you can safely use hydrate. As a result of above, if you are server side rendering and using `ReactDOM.hydrate` you must supply `MediaQueryProvider` a `values` prop.
## Browser Support
The oldest browser we support is IE11,
if you want to support even older browsers please make sure you are using a polyfill for [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) such as [`babel-polyfill`](https://babeljs.io/docs/usage/polyfill/).
## Testing Components
Because the media queries and context are abstracted out you can easily test components with or without the `withMedia` HOC, just ensure you export your component base without the HOC as well, eg:
```javascript
export const BaseMyComponent = MyComponent;
export default withMedia(MyComponent);
```
Then in your React tests you can import like:
```javascript
import { BaseMyComponent } from 'location_of_my_component';
```
And unit test the component without having to worry about context
## Thanks
Big thanks to the maintainers of these repos
- https://github.com/jxnblk/react-media-context
- https://github.com/contra/react-responsive
Both libraries are a bit similar, but my original use case required the extra advantages listed in [Why use this?](#why-use-this)