@samitouri / QOS-React / commits / 6938dcaacb

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);