{"id":16401386,"url":"https://github.com/thearnica/react-media-match","last_synced_at":"2025-04-06T04:14:36.562Z","repository":{"id":33232721,"uuid":"133228724","full_name":"thearnica/react-media-match","owner":"thearnica","description":"React made responsible - media queries backed by state machinery","archived":false,"fork":false,"pushed_at":"2024-05-12T02:07:24.000Z","size":2629,"stargazers_count":130,"open_issues_count":35,"forks_count":6,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-10-12T05:30:52.060Z","etag":null,"topics":["media","media-query","react","responsive","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thearnica.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-05-13T10:58:20.000Z","updated_at":"2024-05-12T02:07:28.000Z","dependencies_parsed_at":"2024-06-18T21:18:56.792Z","dependency_job_id":"a9415545-233f-4915-a891-2cb4492cf64b","html_url":"https://github.com/thearnica/react-media-match","commit_stats":null,"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thearnica%2Freact-media-match","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thearnica%2Freact-media-match/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thearnica%2Freact-media-match/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thearnica%2Freact-media-match/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thearnica","download_url":"https://codeload.github.com/thearnica/react-media-match/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247430963,"owners_count":20937875,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["media","media-query","react","responsive","typescript"],"created_at":"2024-10-11T05:30:17.852Z","updated_at":"2025-04-06T04:14:36.399Z","avatar_url":"https://github.com/thearnica.png","language":"TypeScript","funding_links":[],"categories":["📦 Legacy \u0026 Inactive Projects"],"sub_categories":[],"readme":"# react-media-match\n\n[![Build Status](https://travis-ci.org/thearnica/react-media-match.svg?branch=master)](https://travis-ci.org/thearnica/react-media-match)\n[![coverage-badge](https://img.shields.io/codecov/c/github/thearnica/react-media-match.svg?style=flat-square)](https://codecov.io/github/thearnica/react-media-match)\n[![NPM version](https://img.shields.io/npm/v/react-media-match.svg)](https://www.npmjs.com/package/react-media-match)\n[![bundle size](https://badgen.net/bundlephobia/minzip/react-media-match)](https://bundlephobia.com/result?p=react-media-match)\n[![downloads](https://badgen.net/npm/dm/react-media-match)](https://www.npmtrends.com/react-media-match)\n[![Greenkeeper badge](https://badges.greenkeeper.io/thearnica/react-media-match.svg)](https://greenkeeper.io/)\n\nMedia targets and \"sensors\" are not toys - they define the **state** of your Application. Like a Finite **State Machine** `state`.\nHandle it holistically. Do not use react media query - use media match.\n\n- 📦 all required matchers are built in\n- 🐍 mobile-first \"gap-less\", and (!)**bug-less** approach.\n- 💻 SSR friendly. Customize the target rendering mode and `SSR` for any device.\n- 💡 Provides `Media Matchers` to render Components and `Media Pickers` to pick a value depending on the current media.\n- 🎣 Provide `hooks` interface for `pickers`\n- 🧠 Good typing out of the box - written in TypeScript\n- 🚀 more performant than usual - there is only one top level query\n- 🧨 Controllable matchers\n\n### Sandbox\n\nhttps://codesandbox.io/s/react-media-match-example-g28y3\n\n# Usage\n\nUse prebuild matchers or define your own\n\n```js\n// custom\nimport { createMediaMatcher } from 'react-media-match';\nconst customMatcher = createMediaMatcher({\n  portrait: '(orientation: portrait)',\n  landscape: '(orientation: landscape)',\n});\n\n// prebuild\nimport {\n  breakpoints,\n  orientation,\n  darkMode,\n  hover,\n  reducedMotion,\n  // what else?\n} from 'react-media-match/targets';\n\n// ...\norientation.useMedia({\n  portrait: '📱',\n  landscape: '💻', // (well, actually not, but yes)\n});\n```\n\n## Rules\n\n- **Rule 1**: Don't mix **concerns**\n\nYou shall never mix `size` and `orientation`, `hover` and `reduced-motion` - they are different **slices** of a one big state.\n\n💡 If you need to respond to `screen size` and `orientation` - create 2 separate matchers, and use them separately!\n\n- **Rule 2**: Don't match explicit target - think in states\n  For the every case you might have two or more `states`, only one of which can be active in a single point of view\n  - mobile/tablet/desktop - who you are,\n  - portrait/landscape - how you are holding it\n  - hover/no-hover - there is no way they both can be true\n  - and visa versa.\n\n👉 Each Media Query should be responsible only for a single `dimension` - width, height, hover or orientation.\n\n- **Rule 3**: Intervals\n  Started with desktop and mobile? Then added tablet? Then added small mobile, and then large desktop? You shall be ready for a change.\n  All API in react-media-matcher follow the **pick value to the left** pattern, making impossible situations when you might miss a target.\n\n👉 **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.\n\n- **Rule 4**: Match all rules at once\n  Every matcher should match only one consern, and every matcher should handle all possible variations **simultaneously** - it's not about what do to in case of mobile, it's also what to do in any other case.\n\n👉 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.\n\n## Usage\n\n```sh\nnpm install react-media-match\n\nyarn add react-media-match\n```\n\n- render \"forking\"\n\n```jsx\n\u003cMediaMatcher\n  mobile={'render for mobile'}\n  // tablet={\"tablet\"} // mobile will be rendered for a \"skipped\" tablet - \"pick value to the left\"\n  desktop={'render desktop'}\n/\u003e\n```\n\n- hook interface\n\n```js\nconst title = useMedia({\n  mobile: shortName,\n  tablet: name,\n  // desktop: tablet will be used\n});\n```\n\n- custom media\n\n```js\nconst Orientation = createMediaMatcher({\n  portrait: '(orientation: portrait)',\n  landscape: '(orientation: landscape)',\n});\n\nconst height = Orientation.useMedia({\n  portrait: \"50vw\",\n  landscape: \"50vw\"\n});\n\u003cOrientation.Matcher portrait=\"One\" landscape=\"Second\" /\u003e;\n\u003cOrientation.ServerRender portrait=\"rendering on portrait\" landscape=\"rendering on landscape\" /\u003e;\n```\n\n### More examples of usage\n\n```js\nimport { MediaMatcher, ProvideMediaMatchers } from 'react-media-match';\n\n// this component will calculate all Media's and put data into the React Context\n// if you will not provide it - values would be caclucaed only once, on the application start\n// keep in mind - some values (like hoverability) could not change, and it's legal to skip some providers.\n\u003cProvideMediaMatchers\u003e\n  \u003cMediaMatcher\n    mobile={'render for mobile'}\n    // tablet={\"tablet\"} // mobile will be rendered for \"skipped\" tablet\n    desktop={'render desktop'}\n  /\u003e\n  \u003cMediaMatcher\n    mobile={'render for mobile'}\n    tablet={null} // nothing will be rendered for tablet, as long you clearly \"defined\" it\n    desktop={'render desktop'}\n  /\u003e\n  // there are also \"Range\" Components\n  \u003cAbove mobile\u003ewill be rendered on tablet and desktop\u003c/Above\u003e\n  \u003cBelow desktop\u003ewill be rendered on mobile and tablet\u003c/Above\u003e\n  \u003cBelow including desktop\u003e\n    will be rendered on mobile, tablet and desktop\n  \u003c/Below\u003e\n  \u003cMediaMatches\u003e\n    {' '}\n    // will provide matches information via render-props\n    {(matches) =\u003e (\n      \u003cspan\u003e\n        {' '}\n        testing {\n          // pick matching values\n          pickMatch(matches, {\n            mobile: 'mobile',\n            // tablet: \"tablet\", // the same rules are applied here\n            desktop: 'desktop',\n          })\n        }\n      \u003c/span\u003e\n    )}\n  \u003c/MediaMatches\u003e\n  \u003cMediaMatches\u003e\n    {' '}\n    // will provide matches information via render-props\n    {(\n      _,\n      pickThisMatch // you can get pickMatch from MediaMatches\n    ) =\u003e (\n      \u003cspan\u003e\n        {' '}\n        testing {\n          // pick matching values, there is no need to provide \"matches\"\n          pickThisMatch({\n            mobile: 'mobile',\n            // tablet: \"tablet\", // the same rules are applied here\n            desktop: 'desktop',\n          })\n        }\n      \u003c/span\u003e\n    )}\n  \u003c/MediaMatches\u003e\n  // there is also \"hooks\" API for pickMatch\n\u003c/ProvideMediaMatchers\u003e;\n```\n\nPS: Don’t forget to **wrap all this with ProvideMediaMatchers** - without it MediaMatches will always picks the \"last\" branch.\n\n## API\n\nreact-media-match provides an API for \"default\" queries, and a factory method to create custom media queries.\n\n- `createMediaMatcher(breakPoints: { key: string })` - factory for a new API for provided breakpoints.\n  The object with following keys will be returned:\n  - `pickMatch`\n  - `useMedia`\n  - `Matches`\n  - `Matcher`\n  - `Provider`\n  - `Mock`\n  - `ServerRender`\n  - `Consumer`\n\n## Default API\n\nThere is also pre-exported API for default breakpoints - `mobile`, `tablet`, `desktop`\n\n- `pickMatch(mediaMatches, matchers)` - function, returns value from matchers matching `matchers`.\n\n- `useMatch(matchers)` - hook, returns value from matchers matching matches. This call is equal to `pickMatch` with autowired context.\n\n- `ProvideMediaMatchers` - component, calculates media queries and stores them in context.\n\n- `MediaMatches` - component, returns current matchers as a render prop\n\n- `MediaMatcher` - component, renders path for active match\n\n- `Above` - component, renders children above specified point. Or including specified point if `including` prop is set.\n\n- `Below` - component, renders children below specified point. Or including specified point if `including` prop is set.\n\n- `MediaServerRender` - component, helps render server-size\n\n- `MediaConsumer` - React Context Consumer\n\n## Example\n\n- Define secondary Query for orientation\n\n```js\nimport { createMediaMatcher } from 'react-media-match';\n\nconst Orientation = createMediaMatcher({\n  portrait: '(orientation: portrait)',\n  landscape: '(orientation: landscape)',\n});\n\n\u003cOrientation.Match portrait=\"One\" landscape=\"Second\" /\u003e;\n```\n\n### Usage with hooks\n\nKeep in mind - only _value picker_ should be used as a hook, the _render selection_ should\nbe declarative and use `MediaMatcher`.\n\n```js\nconst MyComponent = ({ shortName, name }) =\u003e {\n  const title = useMedia({\n    mobile: shortName,\n    tablet: name,\n  });\n\n  return \u003cspan\u003eHello {title}\u003c/span\u003e;\n};\n```\n\n### Usage in life cycle events\n\n\u003e Requires React16.6+\n\n```js\nimport { MediaConsumer, pickMatch } from 'react-media-match';\n// use createMediaMatcher to create your own matches\n\nclass App extends React.Component {\n  // provide Consumer as a contextType\n  static contextType = MediaConsumer;\n\n  componentDidMount() {\n    // use `pickMatch` matching the consumer\n    pickMatch(this.context, {\n      mobile: 'a',\n      tablet: 'b',\n    });\n  }\n}\n```\n\n## Top level provider\n\nIf you want to react to a _media change_ you **have** to wrap your application with `ProvideMediaMatchers`.\nBut if you don't - you might skip this moment.\n\n### For example - media for \"device pointer type\"\n\nMobile phones(touch devices) don't have \"hover\" effects, while the onces with `mouse` - do support it.\nMore of it - this could not be changed in runtime - device type is constant.\n\nThis information might be quite important - for example you might control _autoFocus_, as long as\n_auto-focusing_ input on a _touch_ device would open a `virtual keyboard`(consuming 50% of the screen), which may be\nnot desired.\n\nIn this case you might omit `ProvideMediaMatchers` and use _default_ values, which would be computed on start time.\n\n```js\nconst HoverMedia = createMediaMatcher({\n  touchDevice: '(hover: none)',\n  mouseDevice: '(hover: hover)',\n});\n\nconst MyComponent = () =\u003e {\n  const autoFocus = HoverMedia.useMedia({\n    touchDevice: false,\n    mouseDevice: true,\n  });\n\n  return \u003cinput autoFocus={autoFocus} /\u003e;\n};\n```\n\n## Server-Side Rendering\n\nThere is no way to support MediaQuery on the Server Side, so the only way to generate the expected result\nis **to mock** a predicted device.\n\nWe are providing a special component which will\n\n- render data in predicted device on server side,\n- hydrate into it on the client side\n- switch to the real values after initial hydration\n\n```js\nimport { MediaMatcher, MediaServerRender } from 'react-media-match';\n\n\u003cMediaServerRender predicted=\"desktop\" hydrated={optionallyTrue}\u003e\n  \u003cMediaMatcher\n    mobile={'render for mobile'}\n    // tablet={\"tablet\"} // mobile will be rendered for \"skipped\" tablet\n    desktop={'render desktop'}\n  /\u003e\n\u003c/MediaServerRender\u003e;\n```\n\n- set `hydrated` to `true` if your application is already _hydrated_ by any reason. **Omit** the field to let\n  `MediaServerRender` handle hydration process automatically.\n\n#### How to predict a device type\n\nYou may use [ua-parser-js](https://github.com/faisalman/ua-parser-js), to detect device type, and pick desired screen resolution, or use [react-ugent](https://github.com/medipass/react-ugent) to make it a bit\nmore declarative.\n\n## Non media based matches\n\nDefine query based on user settings\n\n```js\nimport { MediaMock, ProvideMediaMatchers } from \"react-media-match\";\n\n// override all the data\n\u003cProvideMediaMatchers state={{mobile:true, tablet:false, desktop:false}}\u003e\n   ....\n\u003c/ProvideMediaMatchers\u003e\n\n\u003cMediaMock mobile\u003e\n    ....\n\u003c/MediaMock\u003e\n\n\u003cOrientation.Mock portrait\u003e\n    ....\n\u003c/Orientation.Mock\u003e\n```\n\n# Testing and Mocking\n\nJust provide `state` for ProvideMediaMatchers, and it will control all the nested matchers. Could be used to provide **not media-based** rules.\n\n- `ProvideMediaMatchers` has a `state` parameter, and you might specify it override any information, and control all the nested matchers.\n- `MediaMock` will completely mock media settings.\n\nBoth mocks are not working for `Inline` component.\n\nTesting and mocking are related to SSR rendering, and you may use MediaServerRender for tests and Mocks for SSR as well.\n\n## See also\n\n- [react-media-query-hoc](https://github.com/DomainGroupOSS/react-media-query-hoc) implements the same idea of SSR friendly and Multiple-breakpoints approach.\n\n## Articles\n\n- Dev.to article - [Take the Responsivebility](https://dev.to/thekashey/take-the-responsivebility-3m8f)\n- Medium article - [Adaptive?! Responsive? Reactive!](https://medium.com/@antonkorzunov/adaptive-responsive-reactive-62fb938d6191)\n- Spectrum chat - [React-Media-Match](https://spectrum.chat/thread/2057cb44-ddd7-4fb2-98f8-c1d697bcf62d)\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthearnica%2Freact-media-match","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthearnica%2Freact-media-match","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthearnica%2Freact-media-match/lists"}