From f9dc037d7f5f8c1500ad51159db5ef9c19d66f88 Mon Sep 17 00:00:00 2001 From: Ray Cohen Date: Fri, 19 Jan 2018 17:58:33 -0500 Subject: [PATCH 1/4] provide optional targetWindow prop. paired with @tonyjmnz --- README.md | 5 +++++ modules/Media.js | 19 ++++++++++++----- modules/__tests__/Media-test.js | 38 +++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d524bc3..e075182 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,11 @@ Keys of media query objects are camel-cased and numeric values automatically get See the [json2mq docs](https://github.com/akiran/json2mq/blob/master/README.md#usage) for more examples of queries you can construct using objects. +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). + 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). diff --git a/modules/Media.js b/modules/Media.js index 2a57892..cf21001 100644 --- a/modules/Media.js +++ b/modules/Media.js @@ -14,11 +14,13 @@ class Media extends React.Component { PropTypes.arrayOf(PropTypes.object.isRequired) ]).isRequired, render: PropTypes.func, - children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]) + children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), + targetWindow: PropTypes.object }; static defaultProps = { - defaultMatches: true + defaultMatches: true, + targetWindow: window }; state = { @@ -28,13 +30,20 @@ class Media extends React.Component { updateMatches = () => this.setState({ matches: this.mediaQueryList.matches }); componentWillMount() { - if (typeof window !== "object") return; - let { query } = this.props; + const { targetWindow } = this.props; + + if (typeof targetWindow !== "object") return; + + if (!targetWindow.matchMedia) { + throw new Error( + 'You passed a `targetWindow` prop to `Media` that does not have a `matchMedia` function.' + ); + } if (typeof query !== "string") query = json2mq(query); - this.mediaQueryList = window.matchMedia(query); + this.mediaQueryList = targetWindow.matchMedia(query); this.mediaQueryList.addListener(this.updateMatches); this.updateMatches(); } diff --git a/modules/__tests__/Media-test.js b/modules/__tests__/Media-test.js index 312cef0..b3b38aa 100644 --- a/modules/__tests__/Media-test.js +++ b/modules/__tests__/Media-test.js @@ -122,6 +122,44 @@ describe("A ", () => { }); }); + describe("when a custom targetWindow prop is passed", () => { + beforeEach(() => { + window.matchMedia = createMockMediaMatcher(true); + }); + + it("renders its child", () => { + const testWindow = { + matchMedia: createMockMediaMatcher(false) + }; + + const element = ( + + {matches => (matches ?
hello
:
goodbye
)} +
+ ); + + ReactDOM.render(element, node, () => { + expect(node.firstChild.innerHTML).toMatch(/goodbye/); + }); + }); + + describe("when a non-window prop is passed for targetWindow", () => { + it("errors with a useful message", () => { + const notAWindow = {}; + + const element = ( + + {matches => (matches ?
hello
:
goodbye
)} +
+ ); + + expect(() => { + ReactDOM.render(element, node, () => {}); + }).toThrow("does not have a `matchMedia` function"); + }); + }) + }); + describe("rendered on the server", () => { beforeEach(() => { window.matchMedia = createMockMediaMatcher(true); From f187c005bd6e9f180910093151c0e4d0a95991af Mon Sep 17 00:00:00 2001 From: Raymond Cohen Date: Mon, 22 Jan 2018 21:38:44 -0500 Subject: [PATCH 2/4] don't use defaultProps to default to 'window' to avoid issues in server contexts --- modules/Media.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/Media.js b/modules/Media.js index cf21001..0c9f9ee 100644 --- a/modules/Media.js +++ b/modules/Media.js @@ -19,8 +19,7 @@ class Media extends React.Component { }; static defaultProps = { - defaultMatches: true, - targetWindow: window + defaultMatches: true }; state = { @@ -31,7 +30,7 @@ class Media extends React.Component { componentWillMount() { let { query } = this.props; - const { targetWindow } = this.props; + const targetWindow = this.props.targetWindow || window; if (typeof targetWindow !== "object") return; From 05ecb021e134542d6bd51e938fc43b565d35b10c Mon Sep 17 00:00:00 2001 From: Ray Cohen Date: Tue, 23 Jan 2018 12:26:27 -0500 Subject: [PATCH 3/4] dont reference window until after we confirm it exists with typeof --- modules/Media.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/Media.js b/modules/Media.js index 0c9f9ee..26a2fd4 100644 --- a/modules/Media.js +++ b/modules/Media.js @@ -30,9 +30,10 @@ class Media extends React.Component { componentWillMount() { let { query } = this.props; - const targetWindow = this.props.targetWindow || window; - if (typeof targetWindow !== "object") return; + if (typeof window !== "object") return; + + const targetWindow = this.props.targetWindow || window; if (!targetWindow.matchMedia) { throw new Error( From 75b00051af6c27d8d4ead48d791b47fd91f33e97 Mon Sep 17 00:00:00 2001 From: Ray Cohen Date: Tue, 23 Jan 2018 12:28:34 -0500 Subject: [PATCH 4/4] move variable declaration back to its original position --- modules/Media.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/Media.js b/modules/Media.js index 26a2fd4..fc7aaad 100644 --- a/modules/Media.js +++ b/modules/Media.js @@ -29,10 +29,9 @@ class Media extends React.Component { updateMatches = () => this.setState({ matches: this.mediaQueryList.matches }); componentWillMount() { - let { query } = this.props; - if (typeof window !== "object") return; + let { query } = this.props; const targetWindow = this.props.targetWindow || window; if (!targetWindow.matchMedia) {