Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit e56a198

Browse files
committedSep 3, 2018
Implement two-pass render (closes #81)
1 parent 39df669 commit e56a198

File tree

3 files changed

+71
-20
lines changed

3 files changed

+71
-20
lines changed
 

‎modules/Media.js

Lines changed: 54 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,31 +24,51 @@ class Media extends React.Component {
2424

2525
queries = [];
2626

27-
state = {
28-
matches:
29-
this.props.defaultMatches ||
30-
Object.keys(this.props.queries).reduce(
31-
(acc, key) => ({ ...acc, [key]: true }),
32-
{}
33-
)
34-
};
27+
constructor(props) {
28+
super(props);
29+
30+
if (typeof window !== "object") {
31+
// In case we're rendering on the server
32+
this.state = {
33+
matches:
34+
this.props.defaultMatches ||
35+
Object.keys(this.props.queries).reduce(
36+
(acc, key) => ({ ...acc, [key]: true }),
37+
{}
38+
)
39+
};
40+
return;
41+
}
3542

36-
updateMatches = () => {
37-
const newMatches = this.queries.reduce(
43+
this.initialize();
44+
45+
// Instead of calling this.updateMatches, we manually set the state to prevent
46+
// calling setState, which could trigger an unnecessary second render
47+
this.state = {
48+
matches:
49+
this.props.defaultMatches !== undefined
50+
? this.props.defaultMatches
51+
: this.getMatches()
52+
};
53+
this.onChange();
54+
}
55+
56+
getMatches = () => {
57+
return this.queries.reduce(
3858
(acc, { name, mqList }) => ({ ...acc, [name]: mqList.matches }),
3959
{}
4060
);
41-
this.setState({ matches: newMatches });
42-
43-
const { onChange } = this.props;
44-
if (onChange) {
45-
onChange(newMatches);
46-
}
4761
};
4862

49-
componentWillMount() {
50-
if (typeof window !== "object") return;
63+
updateMatches = () => {
64+
const newMatches = this.getMatches();
5165

66+
this.setState(() => ({
67+
matches: newMatches
68+
}), this.onChange);
69+
};
70+
71+
initialize() {
5272
const targetWindow = this.props.targetWindow || window;
5373

5474
invariant(
@@ -67,8 +87,23 @@ class Media extends React.Component {
6787

6888
return { name, qs, mqList };
6989
});
90+
}
7091

71-
this.updateMatches();
92+
componentDidMount() {
93+
this.initialize();
94+
// If props.defaultMatches has been set, ensure we trigger a two-pass render.
95+
// This is useful for SSR with mismatching defaultMatches vs actual matches from window.matchMedia
96+
// Details: https://github.com/ReactTraining/react-media/issues/81
97+
if (this.props.defaultMatches !== undefined) {
98+
this.updateMatches();
99+
}
100+
}
101+
102+
onChange() {
103+
const { onChange } = this.props;
104+
if (onChange) {
105+
onChange(this.state.matches);
106+
}
72107
}
73108

74109
componentWillUnmount() {

‎modules/__tests__/Media-test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,4 +291,20 @@ describe("A <Media> in browser environment", () => {
291291
});
292292
});
293293
});
294+
295+
describe("when defaultMatches have been passed", () => {
296+
beforeEach(() => {
297+
window.matchMedia = createMockMediaMatcher(false);
298+
});
299+
300+
it("initially overwrites defaultMatches with matches from matchMedia", async () => {
301+
const element = <Media queries={{ matches: "" }} defaultMatches={{ matches: true }}>
302+
{({ matches }) => matches ? <div>fully matched</div> : <div>not matched</div>}
303+
</Media>;
304+
305+
ReactDOM.render(element, node, () => {
306+
expect(node.firstChild.innerHTML).toMatch('not matched');
307+
});
308+
});
309+
})
294310
});

‎package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
"eslint-plugin-jest": "^20.0.3",
4444
"eslint-plugin-react": "^6.0.0",
4545
"gzip-size": "^3.0.0",
46-
"jest": "^20.0.4",
46+
"jest": "^23.5.0",
4747
"pascal-case": "^2.0.1",
4848
"pretty-bytes": "^4.0.2",
4949
"react": "^15.4.1 || ^0.14.7",

0 commit comments

Comments
 (0)
Please sign in to comment.