@randy-ang/react-media-match
v1.10.3
Published
React made responsible
Downloads
6
Maintainers
Readme
react-media-match
Media targets and "sensors" are not toys - they define the state of your Application. Like the Finite State Machine state. Thus - firstly don't mix conserns, and secondary - handle it as a whole.
- 🐍 mobile-first "gap-less", and (!)bug-less approach.
- 💻 SSR friendly. Customize the target rendering mode and
SSR
for any device. - 💡 Provides
Media Matchers
to render Components andMedia Pickers
to pick a value depending on the current media. - 🎣 Provide
hooks
interface forpickers
- 🧠 Good typing out of the box - written in TypeScript
- 🚀 more performant than usual - there is only one top level query
- 🧨 Controllable matchers
Usage
npm install react-media-match
yarn add react-media-match
The core idea is to use object hashes to define how something should look on all targets, protecting from wide bug variations and making everything more declarative and readable.
// MediaMatcher defines a "fork" for all media targets __simultaneously__ - coherence and collocation
<MediaMatcher
mobile={"render for mobile"}
// tablet={"tablet"} // mobile will be rendered for a "skipped" tablet - "pick value to the left"
desktop={"render desktop"}
/>
- You shall never mix
size
andorientation
,hover
andreduced-motion
- they are different slices of a one big state. - For the every case you might have two or more intervals - mobile/tablet/desktop, or portrait/landscape, or hover/no-hover, and visa versa.
- Every matcher should match only one consern, and every matcher should handle all possible variations simultaneously - situation when you forgot to handle some edge case is just not possible.
Sandbox
https://codesandbox.io/s/o7q3zlo0n9
Concepts
Just:
- Define once all your media queries (2-3-4 usual), and then use them simultaneously!
- Define how application should look like on all resolutions.
Each Media Query is responsible only for a single dimension
- width, height or orientation.
- If you have defined what Query should render on mobile, but not everything else - it will always use mobile.
- defined mobile and desktop, but not tablet or laptop? It will use "value to the left".
Pick value to the left is the core concept. It protects you from mistakes, and allows to skip intermediate resolutions, if they should inherit styles from "lesser" query.
If you need to respond to
screen size
andorientation
- create 2 separate matchers, and work with them - separately!
import { MediaMatcher, ProvideMediaMatchers } from "react-media-match";
// this component will calculate all Media's and put data into the React Context
// if you will not provide it - values would be caclucaed only once, on the application start
// keep in mind - some values (like hoverability) could not change, and it's legal to skip some providers.
<ProvideMediaMatchers>
<MediaMatcher
mobile={"render for mobile"}
// tablet={"tablet"} // mobile will be rendered for "skipped" tablet
desktop={"render desktop"}
/>
<MediaMatcher
mobile={"render for mobile"}
tablet={null} // nothing will be rendered for tablet, as long you clearly "defined" it
desktop={"render desktop"}
/>
// there are also "Range" Components
<Above mobile>will be rendered on tablet and desktop</Above>
<Below desktop>will be rendered on mobile and tablet</Above>
<Below including desktop>will be rendered on mobile, tablet and desktop</Above>
<MediaMatches> // will provide matches information via render-props
{matches => (
<span> testing {
// pick matching values
pickMatch(matches, {
mobile: "mobile",
// tablet: "tablet", // the same rules are applied here
desktop: "desktop",
})
}</span>
)}
</MediaMatches>
<MediaMatches> // will provide matches information via render-props
{(_,pickThisMatch) => ( // you can get pickMatch from MediaMatches
<span> testing {
// pick matching values, there is no need to provide "matches"
pickThisMatch({
mobile: "mobile",
// tablet: "tablet", // the same rules are applied here
desktop: "desktop",
})
}</span>
)}
</MediaMatches>
// there is also "hooks" API for pickMatch
</ProvideMediaMatchers>
PS: Don’t forget to wrap all this with ProvideMediaMatchers - without it MediaMatches will always picks the "last" branch.
API
react-media-match provides an API for "default" queries, and a factory method to create custom media queries.
createMediaMatcher(breakPoints: { key: string })
- factory for a new API for provided breakpoints. The object with following keys will be returned:pickMatch
useMedia
Matches
Matcher
Provider
Mock
SSR
Consumer
Default API
There is also pre-exported API for default breakpoints - mobile
, tablet
, desktop
pickMatch(mediaMatches, matchers)
- function, returns value from matchers matchingmatchers
.useMatch(matchers)
- hook, returns value from matchers matching matches. This call is equal topickMatch
with autowired context.ProvideMediaMatchers
- component, calculates media queries and stores them in context.MediaMatches
- component, returns current matchers as a render propMediaMatcher
- component, renders path for active matchAbove
- component, renders children above specified point. Or including specified point ifincluding
prop is set.Below
- component, renders children below specified point. Or including specified point ifincluding
prop is set.MediaServerRender
- component, helps render server-sizeMediaConsumer
- React Context Consumer
Example
- Define secondary Query for orientation
import { createMediaMatcher } from "react-media-match";
const Orientation = createMediaMatcher({
portrait: "(orientation: portrait)",
landscape: "(orientation: landscape)"
});
<Orientation.Match
portrait="One"
landscape="Second"
/>
Usage with hooks
Keep in mind - only value picker should be used as a hook, the render selection should
be declarative and use MediaMatcher
.
const MyComponent = ({shortName, name}) => {
const title = useMedia({
mobile: shortName,
tablet: name,
});
return <span>Hello {title}</span>
}
Usage in life cycle events
Requires React16.6+
import {MediaConsumer, pickMatch} from 'react-media-match';
// use createMediaMatcher to create your own matches
class App extends React.Component {
// provide Consumer as a contextType
static contextType = MediaConsumer;
componentDidMount() {
// use `pickMatch` matching the consumer
pickMatch(this.context, {
mobile: 'a',
tablet: 'b'
})
}
}
Top level provider
If you want to react to a media change you have to wrap your application with ProvideMediaMatchers
.
But if you don't - you might skip this moment.
For example - media for "device pointer type"
Mobile phones(touch devices) don't have "hover" effects, while the onces with mouse
- do support it.
More of it - this could not be changed in runtime - device type is constant.
This information might be quite important - for example you might control autoFocus, as long as
auto-focusing input on a touch device would open a virtual keyboard
(consuming 50% of the screen), which may be
not desired.
In this case you might omit ProvideMediaMatchers
and use default values, which would be computed on start time.
const HoverMedia = createMediaMatcher({
touchDevice: "(hover: none)",
mouseDevice: "(hover: hover)",
});
const MyComponent = () => {
const autoFocus = HoverMedia.useMedia({
touchDevice: false,
mouseDevice: true,
});
return <input autoFocus={autoFocus}/>
}
Server-Side Rendering
There is no way to support MediaQuery on the Server Side, so the only way to generate the expected result is to mock a predicted device.
We are providing a special component which will mock data only on server size, and compare predicted media on componentMount on client size.
It also has a special prop hydrated
which will lead to forced react tree remount
in case prediction was wrong, and rendered tree will not match hydrated one.
(use only in case of ReactDOM.hydrated
)
import { MediaMatcher, MediaServerRender } from "react-media-match";
<MediaServerRender predicted="desktop" hydrated>
<MediaMatcher
mobile={"render for mobile"}
// tablet={"tablet"} // mobile will be rendered for "skipped" tablet
desktop={"render desktop"}
/>
</MediaServerRender>
If prediction has failed - it will inform you, and might help to mitigate rendering issues.
How to predict a device type
You may use ua-parser-js, to detect device type, and pick desired screen resolution, or use react-ugent to make it a bit more declarative.
Non media based matches
Define query based on user settings
import { MediaMock, ProvideMediaMatchers } from "react-media-match";
// override all the data
<ProvideMediaMatchers state={{mobile:true, tablet:false, desktop:false}}>
....
</ProvideMediaMatchers>
<MediaMock mobile>
....
</MediaMock>
<Orientation.Mock portrait>
....
</Orientation.Mock>
Testing and Mocking
Just provide state
for ProvideMediaMatchers, and it will control all the nested matchers. Could be used to provide not media-based rules.
ProvideMediaMatchers
has astate
parameter, and you might specify it override any information, and control all the nested matchers.MediaMock
will completely mock media settings.
Both mocks are not working for Inline
component.
Testing and mocking are related to SSR rendering, and you may use MediaServerRender for tests and Mocks for SSR as well.
See also
- react-media-query-hoc implements the same idea of SSR friendly and Multiple-breakpoints approach.
Articles
- Dev.to article - Take the Responsivebility
- Medium article - Adaptive?! Responsive? Reactive!
- Spectrum chat - React-Media-Match
License
MIT