Show deprecated context object warnings usage in ReactDOM server (#14033)
* Applies context object warnings to ReactDOM server
Dominic Gannaway committed
Nov 7, 2018 at 17:19 UTC
aa1ffe4e779b8a6da4317be619ff4f0d3d7d02bb
2 files changed
+130
-8
packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js
+99
@@ -249,5 +249,104 @@ describe('ReactDOMServerIntegration', () => {
249
expect(e.querySelector('#language2').textContent).toBe('sanskrit');
250
expect(e.querySelector('#language3').textContent).toBe('french');
251
});
252
+
253
+ itRenders(
254
+ 'should warn with an error message when using Context as consumer in DEV',
255
+ async render => {
256
+ const Theme = React.createContext('dark');
257
+ const Language = React.createContext('french');
258
+
259
+ const App = () => (
260
+ <div>
261
+ <Theme.Provider value="light">
262
+ <Language.Provider value="english">
263
+ <Theme.Provider value="dark">
264
+ <Theme>{theme => <div id="theme1">{theme}</div>}</Theme>
265
+ </Theme.Provider>
266
+ </Language.Provider>
267
+ </Theme.Provider>
268
+ </div>
269
+ );
270
+ // We expect 1 error.
271
+ await render(<App />, 1);
272
+ },
273
+ );
274
+
275
+ // False positive regression test.
276
+ itRenders(
277
+ 'should not warn when using Consumer from React < 16.6 with newer renderer',
278
+ async render => {
279
+ const Theme = React.createContext('dark');
280
+ const Language = React.createContext('french');
281
+ // React 16.5 and earlier didn't have a separate object.
282
+ Theme.Consumer = Theme;
283
+
284
+ const App = () => (
285
+ <div>
286
+ <Theme.Provider value="light">
287
+ <Language.Provider value="english">
288
+ <Theme.Provider value="dark">
289
+ <Theme>{theme => <div id="theme1">{theme}</div>}</Theme>
290
+ </Theme.Provider>
291
+ </Language.Provider>
292
+ </Theme.Provider>
293
+ </div>
294
+ );
295
+ // We expect 0 errors.
296
+ await render(<App />, 0);
297
+ },
298
+ );
299
+
300
+ itRenders(
301
+ 'should warn with an error message when using nested context consumers in DEV',
302
+ async render => {
303
+ const App = () => {
304
+ const Theme = React.createContext('dark');
305
+ const Language = React.createContext('french');
306
+
307
+ return (
308
+ <div>
309
+ <Theme.Provider value="light">
310
+ <Language.Provider value="english">
311
+ <Theme.Provider value="dark">
312
+ <Theme.Consumer.Consumer>
313
+ {theme => <div id="theme1">{theme}</div>}
314
+ </Theme.Consumer.Consumer>
315
+ </Theme.Provider>
316
+ </Language.Provider>
317
+ </Theme.Provider>
318
+ </div>
319
+ );
320
+ };
321
+ // We expect 1 error.
322
+ await render(<App />, 1);
323
+ },
324
+ );
325
+
326
+ itRenders(
327
+ 'should warn with an error message when using Context.Consumer.Provider DEV',
328
+ async render => {
329
+ const App = () => {
330
+ const Theme = React.createContext('dark');
331
+ const Language = React.createContext('french');
332
+
333
+ return (
334
+ <div>
335
+ <Theme.Provider value="light">
336
+ <Language.Provider value="english">
337
+ <Theme.Consumer.Provider value="dark">
338
+ <Theme.Consumer>
339
+ {theme => <div id="theme1">{theme}</div>}
340
+ </Theme.Consumer>
341
+ </Theme.Consumer.Provider>
342
+ </Language.Provider>
343
+ </Theme.Provider>
344
+ </div>
345
+ );
346
+ };
347
+ // We expect 1 error.
348
+ await render(<App />, 1);
349
+ },
350
+ );
351
});
352
});
packages/react-dom/src/server/ReactPartialRenderer.js
+31
-8
@@ -8,11 +8,7 @@
8
*/
9
10
import type {ReactElement} from 'shared/ReactElementType';
11
-import type {
12
- ReactProvider,
13
- ReactConsumer,
14
- ReactContext,
15
-} from 'shared/ReactTypes';
11
+import type {ReactProvider, ReactContext} from 'shared/ReactTypes';
12
13
import React from 'react';
14
import invariant from 'shared/invariant';
@@ -93,6 +89,7 @@ let validatePropertiesInDevelopment = (type, props) => {};
89
let pushCurrentDebugStack = (stack: Array<Frame>) => {};
90
let pushElementToDebugStack = (element: ReactElement) => {};
91
let popCurrentDebugStack = () => {};
92
+let hasWarnedAboutUsingContextAsConsumer = false;
93
94
if (__DEV__) {
95
ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
@@ -1061,9 +1058,35 @@ class ReactDOMServerRenderer {
1058
return '';
1059
}
1060
case REACT_CONTEXT_TYPE: {
1064
- const consumer: ReactConsumer<any> = (nextChild: any);
1065
- const nextProps: any = consumer.props;
1066
- const nextValue = consumer.type._currentValue;
1061
+ let reactContext = (nextChild: any).type;
1062
+ // The logic below for Context differs depending on PROD or DEV mode. In
1063
+ // DEV mode, we create a separate object for Context.Consumer that acts
1064
+ // like a proxy to Context. This proxy object adds unnecessary code in PROD
1065
+ // so we use the old behaviour (Context.Consumer references Context) to
1066
+ // reduce size and overhead. The separate object references context via
1067
+ // a property called "_context", which also gives us the ability to check
1068
+ // in DEV mode if this property exists or not and warn if it does not.
1069
+ if (__DEV__) {
1070
+ if ((reactContext: any)._context === undefined) {
1071
+ // This may be because it's a Context (rather than a Consumer).
1072
+ // Or it may be because it's older React where they're the same thing.
1073
+ // We only want to warn if we're sure it's a new React.
1074
+ if (reactContext !== reactContext.Consumer) {
1075
+ if (!hasWarnedAboutUsingContextAsConsumer) {
1076
+ hasWarnedAboutUsingContextAsConsumer = true;
1077
+ warning(
1078
+ false,
1079
+ 'Rendering <Context> directly is not supported and will be removed in ' +
1080
+ 'a future major release. Did you mean to render <Context.Consumer> instead?',
1081
+ );
1082
+ }
1083
+ }
1084
+ } else {
1085
+ reactContext = (reactContext: any)._context;
1086
+ }
1087
+ }
1088
+ const nextProps: any = (nextChild: any).props;
1089
+ const nextValue = reactContext._currentValue;
1090
1091
const nextChildren = toArray(nextProps.children(nextValue));
1092
const frame: Frame = {