Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: ReactTraining/react-media
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.7.0
Choose a base ref
...
head repository: ReactTraining/react-media
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.9.0
Choose a head ref
Loading
Showing with 10,786 additions and 3,827 deletions.
  1. +8 −8 {modules → }/.eslintrc
  2. +4 −4 .gitignore
  3. +1 −0 .prettierignore
  4. +26 −0 .size-snapshot.json
  5. +24 −16 .travis.yml
  6. +24 −0 CHANGES.md
  7. +47 −1 README.md
  8. +17 −0 index.d.ts
  9. +7 −0 index.js
  10. +23 −0 jest.config.js
  11. +5 −1 modules/.babelrc
  12. +51 −30 modules/Media.js
  13. +23 −0 modules/MediaQueryList.js
  14. +54 −36 modules/__tests__/Media-test.js
  15. +1 −7 modules/index.js
  16. +10,312 −0 package-lock.json
  17. +31 −26 package.json
  18. +128 −0 rollup.config.js
  19. +0 −47 scripts/build.js
  20. +0 −52 scripts/config.js
  21. +0 −29 webpack.config.js
  22. +0 −3,570 yarn.lock
16 changes: 8 additions & 8 deletions modules/.eslintrc → .eslintrc
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
{
"parser": "babel-eslint",
"plugins": [
"import",
"react"
],
"env": {
"browser": true
},
"plugins": ["import", "react"],
"extends": [
"eslint:recommended",
"plugin:import/errors",
"plugin:react/recommended"
],
"rules": {
"array-bracket-spacing": [ 2, "always" ],
"react/display-name": 0,
"semi": [ 2, "never" ]
"globals": {
"__DEV__": true
},
"settings": {
"react": {
"version": "15"
}
}
}
8 changes: 4 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
node_modules
/node_modules/

cjs
esm
umd
/cjs/
/esm/
/umd/
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package.json
26 changes: 26 additions & 0 deletions .size-snapshot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"esm/react-media.js": {
"bundled": 3856,
"minified": 2318,
"gzipped": 885,
"treeshaked": {
"rollup": {
"code": 1928,
"import_statements": 272
},
"webpack": {
"code": 3186
}
}
},
"umd/react-media.js": {
"bundled": 34826,
"minified": 10989,
"gzipped": 3946
},
"umd/react-media.min.js": {
"bundled": 11533,
"minified": 4294,
"gzipped": 1936
}
}
40 changes: 24 additions & 16 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
language: node_js
node_js: node
cache:
directories:
- "$HOME/.cache/yarn"
- node_modules
cache: npm
env:
global:
- secure: J/xT+f4cyBtvmi1rNbwNOQcP/dnheS6Ydi0pHlf7PaEPYVauHWVOYt/aANwdkW4mZP/dlTpvG+J56DkDypvj6U7Q8xL+66PCWpP7geFkwz0kAzD0Els+zSdzpjSoAf4b7d+k8m+3hcjO+BePsDashnx4kLXtRAMbfPMsEaKyAyhMhod/dpBV69vXOTIeJMjPDoScl9LMDOoYGmwAYGRwqRmnH8twGfCknvqfns937r8bHXp6CwMVNhFi82nbGFcO5YDwQNjsp0yg0tzK1xXcIRwG86IABAIBlxvahmi6lsLeGnn21VwoAbYuc6zMj4jlhwjnj0Ko4faV1cQrUVAg5H2C6EdLHij/ufdcG9dwdNGpj9oEiUY97lZrBrmj9RDqGSN/ajA+CZqfjM0ohWXXytieMaCM6ldNF2alYRFaEcoGmHAOUW0Vw5NtUtpvA+cWwwGbxwWhEM+ZBL1/McdWJbH9M6FRj0M6LEB2AzjbtVpU7e74uRcgiwa5BeZYrZ+De9PpTK5XIS5yUTO6xxQ1hH1R6mtslfZTG9hQW/f4gmC/jUEn2OHJ+ZUTXU436zM8g7Q/Iz3DSADhvMFZ3G7ppCOF9csqYMm70lcH3vvMw4uWh/cyVCW70PFou7OMZju11rXxA2C+rzAmmiTqqjEoyDI1dSKi2fImIX2wMZaTOJY=
- NPM_TAG=$([[ "$TRAVIS_TAG" == *-* ]] && echo "next" || echo "latest")
deploy:
provider: npm
email: mjijackson@gmail.com
api_key: "$NPM_TOKEN"
tag: "$NPM_TAG"
skip_cleanup: true
on:
all_branches: true
condition: '"$TRAVIS_TAG" =~ ^v[0-9]'
- TEST_ENV=cjs BUILD_ENV=cjs
- TEST_ENV=umd BUILD_ENV=umd
- TEST_ENV=source
before_script:
- ([[ -z "$BUILD_ENV" ]] || npm run build)
script:
- npm run lint
- npm test
jobs:
include:
- stage: Release
if: tag =~ ^v[0-9]
env: NPM_TAG=$([[ "$TRAVIS_TAG" == *-* ]] && echo "next" || echo "latest")
script: echo "Releasing $TRAVIS_TAG to npm with tag \"$NPM_TAG\" ..."
deploy:
provider: npm
skip_cleanup: true
tag: "$NPM_TAG"
email: npm@mjackson.me
api_key:
secure: Vh46fcq28yrIrhg4I72kPX1B+iZ6/b+2kZVtgYOvZLIpEGB0rsWgfIUqKAfmLX3oh9R72Vfu70mwdEZ4nkql2Qr+c5PaFXHUfTlSeuCiVaEriw2NK4iDRZ8fhMa0VpMd5Jm86Y3RyLUGr4AfAmikGzeijhH70ooZ+EGql39AwdIIktw7OtqopB3WxkMuV6UVmlG68067p65H/V4cf+Kzhy4XlxsGkKgxQ3jm1NgtEG5x9SrpYnOkfwcHo+GaoEvSXXY88WHkFA/YEj0fkeLbodeT2NFKx9cboo2rMB0n55khbfqgTnsUtSPRj1LJQDGMU7fuycC+/Xmb1sZmsKh39bDKgW1Qyw254j3ICMOp+5LGRlQRwzGFLlKup8fEnNevv1jo0znT0TtuNfdw9OENrCZeIHtZmf4dm+uSbpGIfG6T9UfoBhG6ya3O08J5eJCBVUoeHZZaxZ+ufRko2ucfFUx7m755TIsmP74RwF5FOBDTnZY6Hip11rpD32vNPfwAOQe0ra04QpYp5P4jSDUG9/194BKIUnOgFDq5C1JOXsNgOxQuQMiYD7jNkUWNPr1UidScaPhYTwvH+Yi7GkKb/oo49uuhuvOwojAMRpHrsfOKV3eb/vYRxR7tiyVRyYzw4JAkmBtNQEZ9ipqsM31YPIIek/bwKvLkn94+499P/Ks=
on:
tags: true
24 changes: 24 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
## [v1.9.0]
> INSERT DATE
- Add Typescript definitions [#76](https://github.com/ReactTraining/react-media/pull/76)
- Add `onChange` callback [#95](https://github.com/ReactTraining/react-media/pull/95)
- Add workaround for Safari not clearing up event handlers properly [#101](https://github.com/ReactTraining/react-media/pull/101)

[v1.9.0]: https://github.com/ReactTraining/react-media/compare/v1.7.0...v1.9.0

## [v1.7.0]
> Feb 1, 2018
- Add optional `targetWindow` prop [#78](https://github.com/ReactTraining/react-media/pull/78)

[v1.7.0]: https://github.com/ReactTraining/react-media/compare/v1.6.0...v1.7.0

## [v1.6.0]
> Jul 11, 2017
- Add support for multiple queries [#39](https://github.com/ReactTraining/react-media/pull/39)
- Add optional `defaultMatches` prop [#50](https://github.com/ReactTraining/react-media/pull/50)

[v1.6.0]: https://github.com/ReactTraining/react-media/compare/v1.5.0...v1.6.0

## [v1.5.0]
> Feb 17, 2017
48 changes: 47 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -60,7 +60,7 @@ class App extends React.Component {
}
```

If you render a `<Media>` component on the server, it always matches.
If you render a `<Media>` component on the server, it will match by default. You can override the default behavior by setting the `defaultMatches` prop, see [below](#defaultmatches-prop-for-server-side-rendering) for details.

If you use a regular React element as `children` (i.e. `<Media><SomethingHere/></Media>`) it will be rendered if the query matches. However, _you may end up creating a bunch of elements that won't ever actually be rendered to the page_ (i.e. you'll do a lot of unnecessary `createElement`s on each `render`). Thus, a `children` **function** (i.e. `<Media>{matches => ...}</Media>`) is the preferred API. Then you can decide in the callback which elements to create based on the result of the query.

@@ -115,10 +115,56 @@ Keys of media query objects are camel-cased and numeric values automatically get

An optional `targetWindow` prop can be specified if you want the `query` to be evaluated against a different window object than the one the code is running in. This can be useful for example if you are rendering part of your component tree to an iframe or [a popup window](https://hackernoon.com/using-a-react-16-portal-to-do-something-cool-2a2d627b0202).

There is also an optional `onChange` prop, which is a callback function that will be invoked when the status of the media query changes. This can be useful if you need to do some imperative stuff in addition to the declarative approach `react-media` already provides.

```jsx
import React from "react";
import Media from "react-media";

class App extends React.Component {
render() {
return (
<div>
<Media
query="(max-width: 599px)"
onChange={matches =>
matches
? alert("The document is less than 600px wide.")
: alert("The document is at least 600px wide.")
}
/>
</div>
);
}
}
```

If you're curious about how react-media differs from [react-responsive](https://github.com/contra/react-responsive), please see [this comment](https://github.com/ReactTraining/react-media/issues/70#issuecomment-347774260).

Enjoy!

### `defaultMatches` prop for server-side rendering

This component comes with a `defaultMatches` prop and its default is set to true.

When rendering on the server you can use the `defaultMatches` prop to set the initial state on the server to match whatever you think it will be on the client. You can detect the user's device by analyzing the user-agent string from the HTTP request in your server-side rendering code.

```js
initialState = {
device: 'mobile' // add your own guessing logic here
};

<div>
<Media query="(max-width: 500px)" defaultMatches={state.device === 'mobile'} render={() => (
<Text>Render me below medium breakpoint.</Text>
)}/>

<Media query="(min-width: 501px)" defaultMatches={state.device === 'desktop'} render={() => (
<Text>Render me above medium breakpoint.</Text>
)}/>
</div>
```

## About

`react-media` is developed and maintained by [React Training](https://reacttraining.com). If you're interested in learning more about what React can do for your company, please [get in touch](mailto:hello@reacttraining.com)!
17 changes: 17 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as React from "react";
export interface MediaQueryObject {
[id: string]: boolean | number | string;
}
export interface MediaProps {
query: string | MediaQueryObject | MediaQueryObject[];
defaultMatches?: boolean;
children?: ((matches: boolean) => React.ReactNode) | React.ReactNode;
render?: () => React.ReactNode;
targetWindow?: Window;
onChange?: (matches: boolean) => void;
}
/**
* Conditionally renders based on whether or not a media query matches.
*/
declare class Media extends React.Component<MediaProps> {}
export default Media;
7 changes: 7 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-media.min.js');
} else {
module.exports = require('./cjs/react-media.js');
}
23 changes: 23 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
let mappedModule;
switch (process.env.TEST_ENV) {
case 'cjs':
mappedModule = '<rootDir>/cjs/react-media.js';
break;
case 'umd':
mappedModule = '<rootDir>/umd/react-media.js';
break;
case 'source':
default:
mappedModule = '<rootDir>/modules/index.js';
}

module.exports = {
globals: {
__DEV__: true
},
moduleNameMapper: {
'^react-media$': mappedModule
},
testMatch: ['**/__tests__/**/*-test.js'],
testURL: 'http://localhost/'
};
6 changes: 5 additions & 1 deletion modules/.babelrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
{
"presets": [["env", { "loose": true }], "stage-1", "react"]
"presets": [["@babel/env", { "loose": true }], "@babel/react"],
"plugins": [
"@babel/proposal-class-properties",
"@babel/proposal-export-default-from"
]
}
81 changes: 51 additions & 30 deletions modules/Media.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,14 @@
import React from "react";
import PropTypes from "prop-types";
import json2mq from "json2mq";
import React from 'react';
import PropTypes from 'prop-types';
import invariant from 'invariant';
import json2mq from 'json2mq';

import MediaQueryList from './MediaQueryList';

/**
* Conditionally renders based on whether or not a media query matches.
*/
class Media extends React.Component {
static propTypes = {
defaultMatches: PropTypes.bool,
query: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object,
PropTypes.arrayOf(PropTypes.object.isRequired)
]).isRequired,
render: PropTypes.func,
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
targetWindow: PropTypes.object
};

static defaultProps = {
defaultMatches: true
};
@@ -26,45 +17,75 @@ class Media extends React.Component {
matches: this.props.defaultMatches
};

updateMatches = () => this.setState({ matches: this.mediaQueryList.matches });
updateMatches = () => {
const { matches } = this.mediaQueryList;

this.setState({ matches });

const { onChange } = this.props;
if (onChange) {
onChange(matches);
}
};

componentWillMount() {
if (typeof window !== "object") return;
if (typeof window !== 'object') return;

let { query } = this.props;
const targetWindow = this.props.targetWindow || window;

if (!targetWindow.matchMedia) {
throw new Error(
'You passed a `targetWindow` prop to `Media` that does not have a `matchMedia` function.'
);
}
invariant(
typeof targetWindow.matchMedia === 'function',
'<Media targetWindow> does not support `matchMedia`.'
);

if (typeof query !== "string") query = json2mq(query);
let { query } = this.props;
if (typeof query !== 'string') query = json2mq(query);

this.mediaQueryList = targetWindow.matchMedia(query);
this.mediaQueryList.addListener(this.updateMatches);
this.mediaQueryList = new MediaQueryList(
targetWindow,
query,
this.updateMatches
);
this.updateMatches();
}

componentWillUnmount() {
this.mediaQueryList.removeListener(this.updateMatches);
this.mediaQueryList.cancel();
}

render() {
const { children, render } = this.props;
const { matches } = this.state;

return render
? matches ? render() : null
? matches
? render()
: null
: children
? typeof children === "function"
? typeof children === 'function'
? children(matches)
: !Array.isArray(children) || children.length // Preact defaults to empty children array
? matches ? React.Children.only(children) : null
? matches
? React.Children.only(children)
: null
: null
: null;
}
}

if (__DEV__) {
Media.propTypes = {
defaultMatches: PropTypes.bool,
query: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object,
PropTypes.arrayOf(PropTypes.object.isRequired)
]).isRequired,
render: PropTypes.func,
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
targetWindow: PropTypes.object,
onChange: PropTypes.func
};
}

export default Media;
Loading