Skip to content

Commit f9dc037

Browse files
author
Ray Cohen
committed
provide optional targetWindow prop. paired with @tonyjmnz
1 parent eb3e401 commit f9dc037

File tree

3 files changed

+57
-5
lines changed

3 files changed

+57
-5
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ Keys of media query objects are camel-cased and numeric values automatically get
129129
See the [json2mq docs](https://github.com/akiran/json2mq/blob/master/README.md#usage) for more
130130
examples of queries you can construct using objects.
131131

132+
An optional `targetWindow` prop can be specified if you want the `query` to be
133+
evaluated against a different window object than the one the code is running in.
134+
This can be useful for example if you are rendering part of your component tree
135+
to an iframe or [a popup window](https://hackernoon.com/using-a-react-16-portal-to-do-something-cool-2a2d627b0202).
136+
132137
If you're curious about how react-media differs from
133138
[react-responsive](https://github.com/contra/react-responsive), please see
134139
[this comment](https://github.com/ReactTraining/react-media/issues/70#issuecomment-347774260).

modules/Media.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ class Media extends React.Component {
1414
PropTypes.arrayOf(PropTypes.object.isRequired)
1515
]).isRequired,
1616
render: PropTypes.func,
17-
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func])
17+
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
18+
targetWindow: PropTypes.object
1819
};
1920

2021
static defaultProps = {
21-
defaultMatches: true
22+
defaultMatches: true,
23+
targetWindow: window
2224
};
2325

2426
state = {
@@ -28,13 +30,20 @@ class Media extends React.Component {
2830
updateMatches = () => this.setState({ matches: this.mediaQueryList.matches });
2931

3032
componentWillMount() {
31-
if (typeof window !== "object") return;
32-
3333
let { query } = this.props;
34+
const { targetWindow } = this.props;
35+
36+
if (typeof targetWindow !== "object") return;
37+
38+
if (!targetWindow.matchMedia) {
39+
throw new Error(
40+
'You passed a `targetWindow` prop to `Media` that does not have a `matchMedia` function.'
41+
);
42+
}
3443

3544
if (typeof query !== "string") query = json2mq(query);
3645

37-
this.mediaQueryList = window.matchMedia(query);
46+
this.mediaQueryList = targetWindow.matchMedia(query);
3847
this.mediaQueryList.addListener(this.updateMatches);
3948
this.updateMatches();
4049
}

modules/__tests__/Media-test.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,44 @@ describe("A <Media>", () => {
122122
});
123123
});
124124

125+
describe("when a custom targetWindow prop is passed", () => {
126+
beforeEach(() => {
127+
window.matchMedia = createMockMediaMatcher(true);
128+
});
129+
130+
it("renders its child", () => {
131+
const testWindow = {
132+
matchMedia: createMockMediaMatcher(false)
133+
};
134+
135+
const element = (
136+
<Media query="" targetWindow={testWindow}>
137+
{matches => (matches ? <div>hello</div> : <div>goodbye</div>)}
138+
</Media>
139+
);
140+
141+
ReactDOM.render(element, node, () => {
142+
expect(node.firstChild.innerHTML).toMatch(/goodbye/);
143+
});
144+
});
145+
146+
describe("when a non-window prop is passed for targetWindow", () => {
147+
it("errors with a useful message", () => {
148+
const notAWindow = {};
149+
150+
const element = (
151+
<Media query="" targetWindow={notAWindow}>
152+
{matches => (matches ? <div>hello</div> : <div>goodbye</div>)}
153+
</Media>
154+
);
155+
156+
expect(() => {
157+
ReactDOM.render(element, node, () => {});
158+
}).toThrow("does not have a `matchMedia` function");
159+
});
160+
})
161+
});
162+
125163
describe("rendered on the server", () => {
126164
beforeEach(() => {
127165
window.matchMedia = createMockMediaMatcher(true);

0 commit comments

Comments
 (0)