SSR support for class contextType (#13889)
Sebastian Markbåge committed
Oct 19, 2018 at 11:18 UTC
6938dcaacbffb901df27782b7821836961a5b68d
3 files changed
+295
-6
packages/react-dom/src/__tests__/ReactDOMServerIntegrationClassContextType-test.js
new
+266
@@ -0,0 +1,266 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @emails react-core
8
+ */
9
+
10
+'use strict';
11
+
12
+const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
13
+
14
+let React;
15
+let ReactDOM;
16
+let ReactDOMServer;
17
+
18
+function initModules() {
19
+ // Reset warning cache.
20
+ jest.resetModuleRegistry();
21
+ React = require('react');
22
+ ReactDOM = require('react-dom');
23
+ ReactDOMServer = require('react-dom/server');
24
+
25
+ // Make them available to the helpers.
26
+ return {
27
+ ReactDOM,
28
+ ReactDOMServer,
29
+ };
30
+}
31
+
32
+const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules);
33
+
34
+describe('ReactDOMServerIntegration', () => {
35
+ beforeEach(() => {
36
+ resetModules();
37
+ });
38
+
39
+ describe('class contextType', function() {
40
+ let PurpleContext, RedContext, Context;
41
+ beforeEach(() => {
42
+ Context = React.createContext('none');
43
+
44
+ class Parent extends React.Component {
45
+ render() {
46
+ return (
47
+ <Context.Provider value={this.props.text}>
48
+ {this.props.children}
49
+ </Context.Provider>
50
+ );
51
+ }
52
+ }
53
+ PurpleContext = props => <Parent text="purple">{props.children}</Parent>;
54
+ RedContext = props => <Parent text="red">{props.children}</Parent>;
55
+ });
56
+
57
+ itRenders('class child with context', async render => {
58
+ class ClassChildWithContext extends React.Component {
59
+ static contextType = Context;
60
+ render() {
61
+ const text = this.context;
62
+ return <div>{text}</div>;
63
+ }
64
+ }
65
+
66
+ const e = await render(
67
+ <PurpleContext>
68
+ <ClassChildWithContext />
69
+ </PurpleContext>,
70
+ );
71
+ expect(e.textContent).toBe('purple');
72
+ });
73
+
74
+ itRenders('class child without context', async render => {
75
+ class ClassChildWithoutContext extends React.Component {
76
+ render() {
77
+ // this should render blank; context isn't passed to this component.
78
+ return (
79
+ <div>{typeof this.context === 'string' ? this.context : ''}</div>
80
+ );
81
+ }
82
+ }
83
+
84
+ const e = await render(
85
+ <PurpleContext>
86
+ <ClassChildWithoutContext />
87
+ </PurpleContext>,
88
+ );
89
+ expect(e.textContent).toBe('');
90
+ });
91
+
92
+ itRenders('class child with wrong context', async render => {
93
+ class ClassChildWithWrongContext extends React.Component {
94
+ static contextType = Context;
95
+ render() {
96
+ // this should render blank; context.foo isn't passed to this component.
97
+ return <div id="classWrongChild">{this.context.foo}</div>;
98
+ }
99
+ }
100
+
101
+ const e = await render(
102
+ <PurpleContext>
103
+ <ClassChildWithWrongContext />
104
+ </PurpleContext>,
105
+ );
106
+ expect(e.textContent).toBe('');
107
+ });
108
+
109
+ itRenders('with context passed through to a grandchild', async render => {
110
+ class Grandchild extends React.Component {
111
+ static contextType = Context;
112
+ render() {
113
+ return <div>{this.context}</div>;
114
+ }
115
+ }
116
+
117
+ const Child = props => <Grandchild />;
118
+
119
+ const e = await render(
120
+ <PurpleContext>
121
+ <Child />
122
+ </PurpleContext>,
123
+ );
124
+ expect(e.textContent).toBe('purple');
125
+ });
126
+
127
+ itRenders('a child context overriding a parent context', async render => {
128
+ class Grandchild extends React.Component {
129
+ static contextType = Context;
130
+ render() {
131
+ return <div>{this.context}</div>;
132
+ }
133
+ }
134
+
135
+ const e = await render(
136
+ <PurpleContext>
137
+ <RedContext>
138
+ <Grandchild />
139
+ </RedContext>
140
+ </PurpleContext>,
141
+ );
142
+ expect(e.textContent).toBe('red');
143
+ });
144
+
145
+ itRenders('multiple contexts', async render => {
146
+ const Theme = React.createContext('dark');
147
+ const Language = React.createContext('french');
148
+ class Parent extends React.Component {
149
+ render() {
150
+ return (
151
+ <Theme.Provider value="light">
152
+ <Child />
153
+ </Theme.Provider>
154
+ );
155
+ }
156
+ }
157
+
158
+ function Child() {
159
+ return (
160
+ <Language.Provider value="english">
161
+ <Grandchild />
162
+ </Language.Provider>
163
+ );
164
+ }
165
+
166
+ class ThemeComponent extends React.Component {
167
+ static contextType = Theme;
168
+ render() {
169
+ return <div id="theme">{this.context}</div>;
170
+ }
171
+ }
172
+
173
+ class LanguageComponent extends React.Component {
174
+ static contextType = Language;
175
+ render() {
176
+ return <div id="language">{this.context}</div>;
177
+ }
178
+ }
179
+
180
+ const Grandchild = props => {
181
+ return (
182
+ <div>
183
+ <ThemeComponent />
184
+ <LanguageComponent />
185
+ </div>
186
+ );
187
+ };
188
+
189
+ const e = await render(<Parent />);
190
+ expect(e.querySelector('#theme').textContent).toBe('light');
191
+ expect(e.querySelector('#language').textContent).toBe('english');
192
+ });
193
+
194
+ itRenders('nested context unwinding', async render => {
195
+ const Theme = React.createContext('dark');
196
+ const Language = React.createContext('french');
197
+
198
+ class ThemeConsumer extends React.Component {
199
+ static contextType = Theme;
200
+ render() {
201
+ return this.props.children(this.context);
202
+ }
203
+ }
204
+
205
+ class LanguageConsumer extends React.Component {
206
+ static contextType = Language;
207
+ render() {
208
+ return this.props.children(this.context);
209
+ }
210
+ }
211
+
212
+ const App = () => (
213
+ <div>
214
+ <Theme.Provider value="light">
215
+ <Language.Provider value="english">
216
+ <Theme.Provider value="dark">
217
+ <ThemeConsumer>
218
+ {theme => <div id="theme1">{theme}</div>}
219
+ </ThemeConsumer>
220
+ </Theme.Provider>
221
+ <ThemeConsumer>
222
+ {theme => <div id="theme2">{theme}</div>}
223
+ </ThemeConsumer>
224
+ <Language.Provider value="sanskrit">
225
+ <Theme.Provider value="blue">
226
+ <Theme.Provider value="red">
227
+ <LanguageConsumer>
228
+ {() => (
229
+ <Language.Provider value="chinese">
230
+ <Language.Provider value="hungarian" />
231
+ <LanguageConsumer>
232
+ {language => <div id="language1">{language}</div>}
233
+ </LanguageConsumer>
234
+ </Language.Provider>
235
+ )}
236
+ </LanguageConsumer>
237
+ </Theme.Provider>
238
+ <LanguageConsumer>
239
+ {language => (
240
+ <React.Fragment>
241
+ <ThemeConsumer>
242
+ {theme => <div id="theme3">{theme}</div>}
243
+ </ThemeConsumer>
244
+ <div id="language2">{language}</div>
245
+ </React.Fragment>
246
+ )}
247
+ </LanguageConsumer>
248
+ </Theme.Provider>
249
+ </Language.Provider>
250
+ </Language.Provider>
251
+ </Theme.Provider>
252
+ <LanguageConsumer>
253
+ {language => <div id="language3">{language}</div>}
254
+ </LanguageConsumer>
255
+ </div>
256
+ );
257
+ let e = await render(<App />);
258
+ expect(e.querySelector('#theme1').textContent).toBe('dark');
259
+ expect(e.querySelector('#theme2').textContent).toBe('light');
260
+ expect(e.querySelector('#theme3').textContent).toBe('blue');
261
+ expect(e.querySelector('#language1').textContent).toBe('chinese');
262
+ expect(e.querySelector('#language2').textContent).toBe('sanskrit');
263
+ expect(e.querySelector('#language3').textContent).toBe('french');
264
+ });
265
+ });
266
+});
packages/react-dom/src/server/ReactPartialRenderer.js
+26
-5
@@ -180,6 +180,7 @@ const didWarnAboutBadClass = {};
180
const didWarnAboutDeprecatedWillMount = {};
181
const didWarnAboutUndefinedDerivedState = {};
182
const didWarnAboutUninitializedState = {};
183
+const didWarnAboutInvalidateContextType = {};
184
const valuePropNames = ['value', 'defaultValue'];
185
const newlineEatingTags = {
186
listing: true,
@@ -357,13 +358,33 @@ function checkContextTypes(typeSpecs, values, location: string) {
358
}
359
360
function processContext(type, context) {
360
- const maskedContext = maskContext(type, context);
361
- if (__DEV__) {
362
- if (type.contextTypes) {
363
- checkContextTypes(type.contextTypes, maskedContext, 'context');
361
+ const contextType = type.contextType;
362
+ if (typeof contextType === 'object' && contextType !== null) {
363
+ if (__DEV__) {
364
+ if (contextType.$$typeof !== REACT_CONTEXT_TYPE) {
365
+ let name = getComponentName(type) || 'Component';
366
+ if (!didWarnAboutInvalidateContextType[name]) {
367
+ didWarnAboutInvalidateContextType[type] = true;
368
+ warningWithoutStack(
369
+ false,
370
+ '%s defines an invalid contextType. ' +
371
+ 'contextType should point to the Context object returned by React.createContext(). ' +
372
+ 'Did you accidentally pass the Context.Provider instead?',
373
+ name,
374
+ );
375
+ }
376
+ }
377
}
378
+ return contextType._currentValue;
379
+ } else {
380
+ const maskedContext = maskContext(type, context);
381
+ if (__DEV__) {
382
+ if (type.contextTypes) {
383
+ checkContextTypes(type.contextTypes, maskedContext, 'context');
384
+ }
385
+ }
386
+ return maskedContext;
387
}
366
- return maskedContext;
388
}
389
390
const hasOwnProperty = Object.prototype.hasOwnProperty;
packages/react-reconciler/src/ReactFiberClassComponent.js
+3
-1
@@ -25,9 +25,11 @@ import shallowEqual from 'shared/shallowEqual';
25
import getComponentName from 'shared/getComponentName';
26
import invariant from 'shared/invariant';
27
import warningWithoutStack from 'shared/warningWithoutStack';
28
+import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
29
30
import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
31
import {StrictMode} from './ReactTypeOfMode';
32
+
33
import {
34
enqueueUpdate,
35
processUpdateQueue,
@@ -516,7 +518,7 @@ function constructClassInstance(
518
if (typeof contextType === 'object' && contextType !== null) {
519
if (__DEV__) {
520
if (
519
- contextType.Consumer === undefined &&
521
+ contextType.$$typeof !== REACT_CONTEXT_TYPE &&
522
!didWarnAboutInvalidateContextType.has(ctor)
523
) {
524
didWarnAboutInvalidateContextType.add(ctor);