@samitouri / QOS-React-2 / commits / 0c1ec049f8

Add a feature flag to disable legacy context (#16269)

* Add a feature flag to disable legacy context * Address review - invariant -> warning - Make this.context and context argument actually undefined * Increase test coverage for lifecycles * Also disable it on the server is flag is on * Make this.context {} when disabled, but function context is undefined * Move checks inside

Dan Abramov committed Aug 2, 2019 at 01:21 UTC 0c1ec049f8832d1c27f876844666fda393036800
14 files changed +810 -278
packages/react-dom/src/__tests__/ReactDOMServerIntegrationLegacyContextDisabled-test.internal.js new
+168
@@ -0,0 +1,168 @@
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 ReactFeatureFlags;
17 +let ReactDOMServer;
18 +let ReactTestUtils;
19 +
20 +function initModules() {
21 + // Reset warning cache.
22 + jest.resetModuleRegistry();
23 + React = require('react');
24 + ReactDOM = require('react-dom');
25 + ReactDOMServer = require('react-dom/server');
26 + ReactTestUtils = require('react-dom/test-utils');
27 +
28 + ReactFeatureFlags = require('shared/ReactFeatureFlags');
29 + ReactFeatureFlags.disableLegacyContext = true;
30 +
31 + // Make them available to the helpers.
32 + return {
33 + ReactDOM,
34 + ReactDOMServer,
35 + ReactTestUtils,
36 + };
37 +}
38 +
39 +const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules);
40 +
41 +function formatValue(val) {
42 + if (val === null) {
43 + return 'null';
44 + }
45 + if (val === undefined) {
46 + return 'undefined';
47 + }
48 + if (typeof val === 'string') {
49 + return val;
50 + }
51 + return JSON.stringify(val);
52 +}
53 +
54 +describe('ReactDOMServerIntegrationLegacyContextDisabled', () => {
55 + beforeEach(() => {
56 + resetModules();
57 + });
58 +
59 + itRenders('undefined legacy context with warning', async render => {
60 + class LegacyProvider extends React.Component {
61 + static childContextTypes = {
62 + foo() {},
63 + };
64 + getChildContext() {
65 + return {foo: 10};
66 + }
67 + render() {
68 + return this.props.children;
69 + }
70 + }
71 +
72 + let lifecycleContextLog = [];
73 + class LegacyClsConsumer extends React.Component {
74 + static contextTypes = {
75 + foo() {},
76 + };
77 + shouldComponentUpdate(nextProps, nextState, nextContext) {
78 + lifecycleContextLog.push(nextContext);
79 + return true;
80 + }
81 + UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
82 + lifecycleContextLog.push(nextContext);
83 + }
84 + UNSAFE_componentWillUpdate(nextProps, nextState, nextContext) {
85 + lifecycleContextLog.push(nextContext);
86 + }
87 + render() {
88 + return formatValue(this.context);
89 + }
90 + }
91 +
92 + function LegacyFnConsumer(props, context) {
93 + return formatValue(context);
94 + }
95 + LegacyFnConsumer.contextTypes = {foo() {}};
96 +
97 + function RegularFn(props, context) {
98 + return formatValue(context);
99 + }
100 +
101 + const e = await render(
102 + <LegacyProvider>
103 + <span>
104 + <LegacyClsConsumer />
105 + <LegacyFnConsumer />
106 + <RegularFn />
107 + </span>
108 + </LegacyProvider>,
109 + 3,
110 + );
111 + expect(e.textContent).toBe('{}undefinedundefined');
112 + expect(lifecycleContextLog).toEqual([]);
113 + });
114 +
115 + itRenders('modern context', async render => {
116 + let Ctx = React.createContext();
117 +
118 + class Provider extends React.Component {
119 + render() {
120 + return (
121 + <Ctx.Provider value={this.props.value}>
122 + {this.props.children}
123 + </Ctx.Provider>
124 + );
125 + }
126 + }
127 +
128 + class RenderPropConsumer extends React.Component {
129 + render() {
130 + return <Ctx.Consumer>{value => formatValue(value)}</Ctx.Consumer>;
131 + }
132 + }
133 +
134 + let lifecycleContextLog = [];
135 + class ContextTypeConsumer extends React.Component {
136 + static contextType = Ctx;
137 + shouldComponentUpdate(nextProps, nextState, nextContext) {
138 + lifecycleContextLog.push(nextContext);
139 + return true;
140 + }
141 + UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
142 + lifecycleContextLog.push(nextContext);
143 + }
144 + UNSAFE_componentWillUpdate(nextProps, nextState, nextContext) {
145 + lifecycleContextLog.push(nextContext);
146 + }
147 + render() {
148 + return formatValue(this.context);
149 + }
150 + }
151 +
152 + function FnConsumer() {
153 + return formatValue(React.useContext(Ctx));
154 + }
155 +
156 + const e = await render(
157 + <Provider value="a">
158 + <span>
159 + <RenderPropConsumer />
160 + <ContextTypeConsumer />
161 + <FnConsumer />
162 + </span>
163 + </Provider>,
164 + );
165 + expect(e.textContent).toBe('aaa');
166 + expect(lifecycleContextLog).toEqual([]);
167 + });
168 +});
packages/react-dom/src/__tests__/ReactLegacyContextDisabled-test.internal.js new
+207
@@ -0,0 +1,207 @@
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 +let React;
13 +let ReactDOM;
14 +let ReactFeatureFlags;
15 +
16 +describe('ReactLegacyContextDisabled', () => {
17 + beforeEach(() => {
18 + jest.resetModules();
19 +
20 + React = require('react');
21 + ReactDOM = require('react-dom');
22 + ReactFeatureFlags = require('shared/ReactFeatureFlags');
23 + ReactFeatureFlags.disableLegacyContext = true;
24 + });
25 +
26 + function formatValue(val) {
27 + if (val === null) {
28 + return 'null';
29 + }
30 + if (val === undefined) {
31 + return 'undefined';
32 + }
33 + if (typeof val === 'string') {
34 + return val;
35 + }
36 + return JSON.stringify(val);
37 + }
38 +
39 + it('warns for legacy context', () => {
40 + class LegacyProvider extends React.Component {
41 + static childContextTypes = {
42 + foo() {},
43 + };
44 + getChildContext() {
45 + return {foo: 10};
46 + }
47 + render() {
48 + return this.props.children;
49 + }
50 + }
51 +
52 + let lifecycleContextLog = [];
53 + class LegacyClsConsumer extends React.Component {
54 + static contextTypes = {
55 + foo() {},
56 + };
57 + shouldComponentUpdate(nextProps, nextState, nextContext) {
58 + lifecycleContextLog.push(nextContext);
59 + return true;
60 + }
61 + UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
62 + lifecycleContextLog.push(nextContext);
63 + }
64 + UNSAFE_componentWillUpdate(nextProps, nextState, nextContext) {
65 + lifecycleContextLog.push(nextContext);
66 + }
67 + render() {
68 + return formatValue(this.context);
69 + }
70 + }
71 +
72 + function LegacyFnConsumer(props, context) {
73 + return formatValue(context);
74 + }
75 + LegacyFnConsumer.contextTypes = {foo() {}};
76 +
77 + function RegularFn(props, context) {
78 + return formatValue(context);
79 + }
80 +
81 + const container = document.createElement('div');
82 + expect(() => {
83 + ReactDOM.render(
84 + <LegacyProvider>
85 + <span>
86 + <LegacyClsConsumer />
87 + <LegacyFnConsumer />
88 + <RegularFn />
89 + </span>
90 + </LegacyProvider>,
91 + container,
92 + );
93 + }).toWarnDev(
94 + [
95 + 'LegacyProvider uses the legacy childContextTypes API which is no longer supported. ' +
96 + 'Use React.createContext() instead.',
97 + 'LegacyClsConsumer uses the legacy contextTypes API which is no longer supported. ' +
98 + 'Use React.createContext() with static contextType instead.',
99 + 'LegacyFnConsumer uses the legacy contextTypes API which is no longer supported. ' +
100 + 'Use React.createContext() with React.useContext() instead.',
101 + ],
102 + {withoutStack: true},
103 + );
104 + expect(container.textContent).toBe('{}undefinedundefined');
105 + expect(lifecycleContextLog).toEqual([]);
106 +
107 + // Test update path.
108 + ReactDOM.render(
109 + <LegacyProvider>
110 + <span>
111 + <LegacyClsConsumer />
112 + <LegacyFnConsumer />
113 + <RegularFn />
114 + </span>
115 + </LegacyProvider>,
116 + container,
117 + );
118 + expect(container.textContent).toBe('{}undefinedundefined');
119 + expect(lifecycleContextLog).toEqual([{}, {}, {}]);
120 + ReactDOM.unmountComponentAtNode(container);
121 + });
122 +
123 + it('renders a tree with modern context', () => {
124 + let Ctx = React.createContext();
125 +
126 + class Provider extends React.Component {
127 + render() {
128 + return (
129 + <Ctx.Provider value={this.props.value}>
130 + {this.props.children}
131 + </Ctx.Provider>
132 + );
133 + }
134 + }
135 +
136 + class RenderPropConsumer extends React.Component {
137 + render() {
138 + return <Ctx.Consumer>{value => formatValue(value)}</Ctx.Consumer>;
139 + }
140 + }
141 +
142 + let lifecycleContextLog = [];
143 + class ContextTypeConsumer extends React.Component {
144 + static contextType = Ctx;
145 + shouldComponentUpdate(nextProps, nextState, nextContext) {
146 + lifecycleContextLog.push(nextContext);
147 + return true;
148 + }
149 + UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
150 + lifecycleContextLog.push(nextContext);
151 + }
152 + UNSAFE_componentWillUpdate(nextProps, nextState, nextContext) {
153 + lifecycleContextLog.push(nextContext);
154 + }
155 + render() {
156 + return formatValue(this.context);
157 + }
158 + }
159 +
160 + function FnConsumer() {
161 + return formatValue(React.useContext(Ctx));
162 + }
163 +
164 + const container = document.createElement('div');
165 + ReactDOM.render(
166 + <Provider value="a">
167 + <span>
168 + <RenderPropConsumer />
169 + <ContextTypeConsumer />
170 + <FnConsumer />
171 + </span>
172 + </Provider>,
173 + container,
174 + );
175 + expect(container.textContent).toBe('aaa');
176 + expect(lifecycleContextLog).toEqual([]);
177 +
178 + // Test update path
179 + ReactDOM.render(
180 + <Provider value="a">
181 + <span>
182 + <RenderPropConsumer />
183 + <ContextTypeConsumer />
184 + <FnConsumer />
185 + </span>
186 + </Provider>,
187 + container,
188 + );
189 + expect(container.textContent).toBe('aaa');
190 + expect(lifecycleContextLog).toEqual(['a', 'a', 'a']);
191 + lifecycleContextLog.length = 0;
192 +
193 + ReactDOM.render(
194 + <Provider value="b">
195 + <span>
196 + <RenderPropConsumer />
197 + <ContextTypeConsumer />
198 + <FnConsumer />
199 + </span>
200 + </Provider>,
201 + container,
202 + );
203 + expect(container.textContent).toBe('bbb');
204 + expect(lifecycleContextLog).toEqual(['b', 'b']); // sCU skipped due to changed context value.
205 + ReactDOM.unmountComponentAtNode(container);
206 + });
207 +});
packages/react-dom/src/server/ReactPartialRenderer.js
+37 -21
@@ -21,6 +21,7 @@ import describeComponentFrame from 'shared/describeComponentFrame';
21 import ReactSharedInternals from 'shared/ReactSharedInternals';
22 import {
23 warnAboutDeprecatedLifecycles,
24 + disableLegacyContext,
25 enableSuspenseServerRenderer,
26 enableFundamentalAPI,
27 enableFlareAPI,
@@ -430,7 +431,8 @@ function resolve(
431
432 // Extra closure so queue and replace can be captured properly
433 function processChild(element, Component) {
433 - let publicContext = processContext(Component, context, threadID);
434 + const isClass = shouldConstruct(Component);
435 + const publicContext = processContext(Component, context, threadID, isClass);
436
437 let queue = [];
438 let replace = false;
@@ -458,7 +460,7 @@ function resolve(
460 };
461
462 let inst;
461 - if (shouldConstruct(Component)) {
463 + if (isClass) {
464 inst = new Component(element.props, publicContext, updater);
465
466 if (typeof Component.getDerivedStateFromProps === 'function') {
@@ -650,29 +652,43 @@ function resolve(
652 validateRenderResult(child, Component);
653
654 let childContext;
653 - if (typeof inst.getChildContext === 'function') {
654 - let childContextTypes = Component.childContextTypes;
655 - if (typeof childContextTypes === 'object') {
656 - childContext = inst.getChildContext();
657 - for (let contextKey in childContext) {
658 - invariant(
659 - contextKey in childContextTypes,
660 - '%s.getChildContext(): key "%s" is not defined in childContextTypes.',
655 + if (disableLegacyContext) {
656 + if (__DEV__) {
657 + let childContextTypes = Component.childContextTypes;
658 + if (childContextTypes !== undefined) {
659 + warningWithoutStack(
660 + false,
661 + '%s uses the legacy childContextTypes API which is no longer supported. ' +
662 + 'Use React.createContext() instead.',
663 getComponentName(Component) || 'Unknown',
662 - contextKey,
664 );
665 }
665 - } else {
666 - warningWithoutStack(
667 - false,
668 - '%s.getChildContext(): childContextTypes must be defined in order to ' +
669 - 'use getChildContext().',
670 - getComponentName(Component) || 'Unknown',
671 - );
666 }
673 - }
674 - if (childContext) {
675 - context = Object.assign({}, context, childContext);
667 + } else {
668 + if (typeof inst.getChildContext === 'function') {
669 + let childContextTypes = Component.childContextTypes;
670 + if (typeof childContextTypes === 'object') {
671 + childContext = inst.getChildContext();
672 + for (let contextKey in childContext) {
673 + invariant(
674 + contextKey in childContextTypes,
675 + '%s.getChildContext(): key "%s" is not defined in childContextTypes.',
676 + getComponentName(Component) || 'Unknown',
677 + contextKey,
678 + );
679 + }
680 + } else {
681 + warningWithoutStack(
682 + false,
683 + '%s.getChildContext(): childContextTypes must be defined in order to ' +
684 + 'use getChildContext().',
685 + getComponentName(Component) || 'Unknown',
686 + );
687 + }
688 + }
689 + if (childContext) {
690 + context = Object.assign({}, context, childContext);
691 + }
692 }
693 }
694 return {child, context};
packages/react-dom/src/server/ReactPartialRendererContext.js
+87 -46
@@ -10,6 +10,7 @@
10 import type {ThreadID} from './ReactThreadIDAllocator';
11 import type {ReactContext} from 'shared/ReactTypes';
12
13 +import {disableLegacyContext} from 'shared/ReactFeatureFlags';
14 import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
15 import ReactSharedInternals from 'shared/ReactSharedInternals';
16 import getComponentName from 'shared/getComponentName';
@@ -73,60 +74,100 @@ export function processContext(
74 type: Function,
75 context: Object,
76 threadID: ThreadID,
77 + isClass: boolean,
78 ) {
77 - const contextType = type.contextType;
78 - if (__DEV__) {
79 - if ('contextType' in (type: any)) {
80 - let isValid =
81 - // Allow null for conditional declaration
82 - contextType === null ||
83 - (contextType !== undefined &&
84 - contextType.$$typeof === REACT_CONTEXT_TYPE &&
85 - contextType._context === undefined); // Not a <Context.Consumer>
79 + if (isClass) {
80 + const contextType = type.contextType;
81 + if (__DEV__) {
82 + if ('contextType' in (type: any)) {
83 + let isValid =
84 + // Allow null for conditional declaration
85 + contextType === null ||
86 + (contextType !== undefined &&
87 + contextType.$$typeof === REACT_CONTEXT_TYPE &&
88 + contextType._context === undefined); // Not a <Context.Consumer>
89
87 - if (!isValid && !didWarnAboutInvalidateContextType.has(type)) {
88 - didWarnAboutInvalidateContextType.add(type);
90 + if (!isValid && !didWarnAboutInvalidateContextType.has(type)) {
91 + didWarnAboutInvalidateContextType.add(type);
92
90 - let addendum = '';
91 - if (contextType === undefined) {
92 - addendum =
93 - ' However, it is set to undefined. ' +
94 - 'This can be caused by a typo or by mixing up named and default imports. ' +
95 - 'This can also happen due to a circular dependency, so ' +
96 - 'try moving the createContext() call to a separate file.';
97 - } else if (typeof contextType !== 'object') {
98 - addendum = ' However, it is set to a ' + typeof contextType + '.';
99 - } else if (contextType.$$typeof === REACT_PROVIDER_TYPE) {
100 - addendum = ' Did you accidentally pass the Context.Provider instead?';
101 - } else if (contextType._context !== undefined) {
102 - // <Context.Consumer>
103 - addendum = ' Did you accidentally pass the Context.Consumer instead?';
104 - } else {
105 - addendum =
106 - ' However, it is set to an object with keys {' +
107 - Object.keys(contextType).join(', ') +
108 - '}.';
93 + let addendum = '';
94 + if (contextType === undefined) {
95 + addendum =
96 + ' However, it is set to undefined. ' +
97 + 'This can be caused by a typo or by mixing up named and default imports. ' +
98 + 'This can also happen due to a circular dependency, so ' +
99 + 'try moving the createContext() call to a separate file.';
100 + } else if (typeof contextType !== 'object') {
101 + addendum = ' However, it is set to a ' + typeof contextType + '.';
102 + } else if (contextType.$$typeof === REACT_PROVIDER_TYPE) {
103 + addendum =
104 + ' Did you accidentally pass the Context.Provider instead?';
105 + } else if (contextType._context !== undefined) {
106 + // <Context.Consumer>
107 + addendum =
108 + ' Did you accidentally pass the Context.Consumer instead?';
109 + } else {
110 + addendum =
111 + ' However, it is set to an object with keys {' +
112 + Object.keys(contextType).join(', ') +
113 + '}.';
114 + }
115 + warningWithoutStack(
116 + false,
117 + '%s defines an invalid contextType. ' +
118 + 'contextType should point to the Context object returned by React.createContext().%s',
119 + getComponentName(type) || 'Component',
120 + addendum,
121 + );
122 }
110 - warningWithoutStack(
111 - false,
112 - '%s defines an invalid contextType. ' +
113 - 'contextType should point to the Context object returned by React.createContext().%s',
114 - getComponentName(type) || 'Component',
115 - addendum,
116 - );
123 }
124 }
119 - }
120 - if (typeof contextType === 'object' && contextType !== null) {
121 - validateContextBounds(contextType, threadID);
122 - return contextType[threadID];
125 + if (typeof contextType === 'object' && contextType !== null) {
126 + validateContextBounds(contextType, threadID);
127 + return contextType[threadID];
128 + }
129 + if (disableLegacyContext) {
130 + if (__DEV__) {
131 + if (type.contextTypes) {
132 + warningWithoutStack(
133 + false,
134 + '%s uses the legacy contextTypes API which is no longer supported. ' +
135 + 'Use React.createContext() with static contextType instead.',
136 + getComponentName(type) || 'Unknown',
137 + );
138 + }
139 + }
140 + return emptyObject;
141 + } else {
142 + const maskedContext = maskContext(type, context);
143 + if (__DEV__) {
144 + if (type.contextTypes) {
145 + checkContextTypes(type.contextTypes, maskedContext, 'context');
146 + }
147 + }
148 + return maskedContext;
149 + }
150 } else {
124 - const maskedContext = maskContext(type, context);
125 - if (__DEV__) {
126 - if (type.contextTypes) {
127 - checkContextTypes(type.contextTypes, maskedContext, 'context');
151 + if (disableLegacyContext) {
152 + if (__DEV__) {
153 + if (type.contextTypes) {
154 + warningWithoutStack(
155 + false,
156 + '%s uses the legacy contextTypes API which is no longer supported. ' +
157 + 'Use React.createContext() with React.useContext() instead.',
158 + getComponentName(type) || 'Unknown',
159 + );
160 + }
161 + }
162 + return undefined;
163 + } else {
164 + const maskedContext = maskContext(type, context);
165 + if (__DEV__) {
166 + if (type.contextTypes) {
167 + checkContextTypes(type.contextTypes, maskedContext, 'context');
168 + }
169 }
170 + return maskedContext;
171 }
130 - return maskedContext;
172 }
173 }
packages/react-reconciler/src/ReactFiberBeginWork.js
+24 -4
@@ -57,6 +57,7 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
57 import {
58 debugRenderPhaseSideEffects,
59 debugRenderPhaseSideEffectsForStrictMode,
60 + disableLegacyContext,
61 enableProfilerTimer,
62 enableSchedulerTracing,
63 enableSuspenseServerRenderer,
@@ -615,8 +616,11 @@ function updateFunctionComponent(
616 }
617 }
618
618 - const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
619 - const context = getMaskedContext(workInProgress, unmaskedContext);
619 + let context;
620 + if (!disableLegacyContext) {
621 + const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
622 + context = getMaskedContext(workInProgress, unmaskedContext);
623 + }
624
625 let nextChildren;
626 prepareToReadContext(workInProgress, renderExpirationTime);
@@ -1230,8 +1234,15 @@ function mountIndeterminateComponent(
1234 }
1235
1236 const props = workInProgress.pendingProps;
1233 - const unmaskedContext = getUnmaskedContext(workInProgress, Component, false);
1234 - const context = getMaskedContext(workInProgress, unmaskedContext);
1237 + let context;
1238 + if (!disableLegacyContext) {
1239 + const unmaskedContext = getUnmaskedContext(
1240 + workInProgress,
1241 + Component,
1242 + false,
1243 + );
1244 + context = getMaskedContext(workInProgress, unmaskedContext);
1245 + }
1246
1247 prepareToReadContext(workInProgress, renderExpirationTime);
1248 let value;
@@ -1349,6 +1360,15 @@ function mountIndeterminateComponent(
1360 // Proceed under the assumption that this is a function component
1361 workInProgress.tag = FunctionComponent;
1362 if (__DEV__) {
1363 + if (disableLegacyContext && Component.contextTypes) {
1364 + warningWithoutStack(
1365 + false,
1366 + '%s uses the legacy contextTypes API which is no longer supported. ' +
1367 + 'Use React.createContext() with React.useContext() instead.',
1368 + getComponentName(Component) || 'Unknown',
1369 + );
1370 + }
1371 +
1372 if (
1373 debugRenderPhaseSideEffects ||
1374 (debugRenderPhaseSideEffectsForStrictMode &&
packages/react-reconciler/src/ReactFiberClassComponent.js
+45 -22
@@ -15,6 +15,7 @@ import {Update, Snapshot} from 'shared/ReactSideEffectTags';
15 import {
16 debugRenderPhaseSideEffects,
17 debugRenderPhaseSideEffectsForStrictMode,
18 + disableLegacyContext,
19 warnAboutDeprecatedLifecycles,
20 } from 'shared/ReactFeatureFlags';
21 import ReactStrictModeWarnings from './ReactStrictModeWarnings';
@@ -361,26 +362,46 @@ function checkClassInstance(workInProgress: Fiber, ctor: any, newProps: any) {
362 'property to define contextType instead.',
363 name,
364 );
364 - const noInstanceContextTypes = !instance.contextTypes;
365 - warningWithoutStack(
366 - noInstanceContextTypes,
367 - 'contextTypes was defined as an instance property on %s. Use a static ' +
368 - 'property to define contextTypes instead.',
369 - name,
370 - );
365
372 - if (
373 - ctor.contextType &&
374 - ctor.contextTypes &&
375 - !didWarnAboutContextTypeAndContextTypes.has(ctor)
376 - ) {
377 - didWarnAboutContextTypeAndContextTypes.add(ctor);
366 + if (disableLegacyContext) {
367 + if (ctor.childContextTypes) {
368 + warningWithoutStack(
369 + false,
370 + '%s uses the legacy childContextTypes API which is no longer supported. ' +
371 + 'Use React.createContext() instead.',
372 + name,
373 + );
374 + }
375 + if (ctor.contextTypes) {
376 + warningWithoutStack(
377 + false,
378 + '%s uses the legacy contextTypes API which is no longer supported. ' +
379 + 'Use React.createContext() with static contextType instead.',
380 + name,
381 + );
382 + }
383 + } else {
384 + const noInstanceContextTypes = !instance.contextTypes;
385 warningWithoutStack(
379 - false,
380 - '%s declares both contextTypes and contextType static properties. ' +
381 - 'The legacy contextTypes property will be ignored.',
386 + noInstanceContextTypes,
387 + 'contextTypes was defined as an instance property on %s. Use a static ' +
388 + 'property to define contextTypes instead.',
389 name,
390 );
391 +
392 + if (
393 + ctor.contextType &&
394 + ctor.contextTypes &&
395 + !didWarnAboutContextTypeAndContextTypes.has(ctor)
396 + ) {
397 + didWarnAboutContextTypeAndContextTypes.add(ctor);
398 + warningWithoutStack(
399 + false,
400 + '%s declares both contextTypes and contextType static properties. ' +
401 + 'The legacy contextTypes property will be ignored.',
402 + name,
403 + );
404 + }
405 }
406
407 const noComponentShouldUpdate =
@@ -534,7 +555,7 @@ function constructClassInstance(
555 ): any {
556 let isLegacyContextConsumer = false;
557 let unmaskedContext = emptyContextObject;
537 - let context = null;
558 + let context = emptyContextObject;
559 const contextType = ctor.contextType;
560
561 if (__DEV__) {
@@ -582,7 +603,7 @@ function constructClassInstance(
603
604 if (typeof contextType === 'object' && contextType !== null) {
605 context = readContext((contextType: any));
585 - } else {
606 + } else if (!disableLegacyContext) {
607 unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
608 const contextTypes = ctor.contextTypes;
609 isLegacyContextConsumer =
@@ -785,6 +806,8 @@ function mountClassInstance(
806 const contextType = ctor.contextType;
807 if (typeof contextType === 'object' && contextType !== null) {
808 instance.context = readContext(contextType);
809 + } else if (disableLegacyContext) {
810 + instance.context = emptyContextObject;
811 } else {
812 const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
813 instance.context = getMaskedContext(workInProgress, unmaskedContext);
@@ -885,10 +908,10 @@ function resumeMountClassInstance(
908
909 const oldContext = instance.context;
910 const contextType = ctor.contextType;
888 - let nextContext;
911 + let nextContext = emptyContextObject;
912 if (typeof contextType === 'object' && contextType !== null) {
913 nextContext = readContext(contextType);
891 - } else {
914 + } else if (!disableLegacyContext) {
915 const nextLegacyUnmaskedContext = getUnmaskedContext(
916 workInProgress,
917 ctor,
@@ -1034,10 +1057,10 @@ function updateClassInstance(
1057
1058 const oldContext = instance.context;
1059 const contextType = ctor.contextType;
1037 - let nextContext;
1060 + let nextContext = emptyContextObject;
1061 if (typeof contextType === 'object' && contextType !== null) {
1062 nextContext = readContext(contextType);
1040 - } else {
1063 + } else if (!disableLegacyContext) {
1064 const nextUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
1065 nextContext = getMaskedContext(workInProgress, nextUnmaskedContext);
1066 }
packages/react-reconciler/src/ReactFiberContext.js
+234 -185
@@ -11,6 +11,7 @@ import type {Fiber} from './ReactFiber';
11 import type {StackCursor} from './ReactFiberStack';
12
13 import {isFiberMounted} from 'react-reconciler/reflection';
14 +import {disableLegacyContext} from 'shared/ReactFeatureFlags';
15 import {ClassComponent, HostRoot} from 'shared/ReactWorkTags';
16 import getComponentName from 'shared/getComponentName';
17 import invariant from 'shared/invariant';
@@ -46,14 +47,18 @@ function getUnmaskedContext(
47 Component: Function,
48 didPushOwnContextIfProvider: boolean,
49 ): Object {
49 - if (didPushOwnContextIfProvider && isContextProvider(Component)) {
50 - // If the fiber is a context provider itself, when we read its context
51 - // we may have already pushed its own child context on the stack. A context
52 - // provider should not "see" its own child context. Therefore we read the
53 - // previous (parent) context instead for a context provider.
54 - return previousContext;
50 + if (disableLegacyContext) {
51 + return emptyContextObject;
52 + } else {
53 + if (didPushOwnContextIfProvider && isContextProvider(Component)) {
54 + // If the fiber is a context provider itself, when we read its context
55 + // we may have already pushed its own child context on the stack. A context
56 + // provider should not "see" its own child context. Therefore we read the
57 + // previous (parent) context instead for a context provider.
58 + return previousContext;
59 + }
60 + return contextStackCursor.current;
61 }
56 - return contextStackCursor.current;
62 }
63
64 function cacheContext(
@@ -61,74 +66,98 @@ function cacheContext(
66 unmaskedContext: Object,
67 maskedContext: Object,
68 ): void {
64 - const instance = workInProgress.stateNode;
65 - instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
66 - instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
69 + if (disableLegacyContext) {
70 + return;
71 + } else {
72 + const instance = workInProgress.stateNode;
73 + instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
74 + instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
75 + }
76 }
77
78 function getMaskedContext(
79 workInProgress: Fiber,
80 unmaskedContext: Object,
81 ): Object {
73 - const type = workInProgress.type;
74 - const contextTypes = type.contextTypes;
75 - if (!contextTypes) {
82 + if (disableLegacyContext) {
83 return emptyContextObject;
77 - }
84 + } else {
85 + const type = workInProgress.type;
86 + const contextTypes = type.contextTypes;
87 + if (!contextTypes) {
88 + return emptyContextObject;
89 + }
90
79 - // Avoid recreating masked context unless unmasked context has changed.
80 - // Failing to do this will result in unnecessary calls to componentWillReceiveProps.
81 - // This may trigger infinite loops if componentWillReceiveProps calls setState.
82 - const instance = workInProgress.stateNode;
83 - if (
84 - instance &&
85 - instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
86 - ) {
87 - return instance.__reactInternalMemoizedMaskedChildContext;
88 - }
91 + // Avoid recreating masked context unless unmasked context has changed.
92 + // Failing to do this will result in unnecessary calls to componentWillReceiveProps.
93 + // This may trigger infinite loops if componentWillReceiveProps calls setState.
94 + const instance = workInProgress.stateNode;
95 + if (
96 + instance &&
97 + instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
98 + ) {
99 + return instance.__reactInternalMemoizedMaskedChildContext;
100 + }
101
90 - const context = {};
91 - for (let key in contextTypes) {
92 - context[key] = unmaskedContext[key];
93 - }
102 + const context = {};
103 + for (let key in contextTypes) {
104 + context[key] = unmaskedContext[key];
105 + }
106
95 - if (__DEV__) {
96 - const name = getComponentName(type) || 'Unknown';
97 - checkPropTypes(
98 - contextTypes,
99 - context,
100 - 'context',
101 - name,
102 - getCurrentFiberStackInDev,
103 - );
104 - }
107 + if (__DEV__) {
108 + const name = getComponentName(type) || 'Unknown';
109 + checkPropTypes(
110 + contextTypes,
111 + context,
112 + 'context',
113 + name,
114 + getCurrentFiberStackInDev,
115 + );
116 + }
117
106 - // Cache unmasked context so we can avoid recreating masked context unless necessary.
107 - // Context is created before the class component is instantiated so check for instance.
108 - if (instance) {
109 - cacheContext(workInProgress, unmaskedContext, context);
110 - }
118 + // Cache unmasked context so we can avoid recreating masked context unless necessary.
119 + // Context is created before the class component is instantiated so check for instance.
120 + if (instance) {
121 + cacheContext(workInProgress, unmaskedContext, context);
122 + }
123
112 - return context;
124 + return context;
125 + }
126 }
127
128 function hasContextChanged(): boolean {
116 - return didPerformWorkStackCursor.current;
129 + if (disableLegacyContext) {
130 + return false;
131 + } else {
132 + return didPerformWorkStackCursor.current;
133 + }
134 }
135
136 function isContextProvider(type: Function): boolean {
120 - const childContextTypes = type.childContextTypes;
121 - return childContextTypes !== null && childContextTypes !== undefined;
137 + if (disableLegacyContext) {
138 + return false;
139 + } else {
140 + const childContextTypes = type.childContextTypes;
141 + return childContextTypes !== null && childContextTypes !== undefined;
142 + }
143 }
144
145 function popContext(fiber: Fiber): void {
125 - pop(didPerformWorkStackCursor, fiber);
126 - pop(contextStackCursor, fiber);
146 + if (disableLegacyContext) {
147 + return;
148 + } else {
149 + pop(didPerformWorkStackCursor, fiber);
150 + pop(contextStackCursor, fiber);
151 + }
152 }
153
154 function popTopLevelContextObject(fiber: Fiber): void {
130 - pop(didPerformWorkStackCursor, fiber);
131 - pop(contextStackCursor, fiber);
155 + if (disableLegacyContext) {
156 + return;
157 + } else {
158 + pop(didPerformWorkStackCursor, fiber);
159 + pop(contextStackCursor, fiber);
160 + }
161 }
162
163 function pushTopLevelContextObject(
@@ -136,14 +165,18 @@ function pushTopLevelContextObject(
165 context: Object,
166 didChange: boolean,
167 ): void {
139 - invariant(
140 - contextStackCursor.current === emptyContextObject,
141 - 'Unexpected context found on stack. ' +
142 - 'This error is likely caused by a bug in React. Please file an issue.',
143 - );
144 -
145 - push(contextStackCursor, context, fiber);
146 - push(didPerformWorkStackCursor, didChange, fiber);
168 + if (disableLegacyContext) {
169 + return;
170 + } else {
171 + invariant(
172 + contextStackCursor.current === emptyContextObject,
173 + 'Unexpected context found on stack. ' +
174 + 'This error is likely caused by a bug in React. Please file an issue.',
175 + );
176 +
177 + push(contextStackCursor, context, fiber);
178 + push(didPerformWorkStackCursor, didChange, fiber);
179 + }
180 }
181
182 function processChildContext(
@@ -151,87 +184,95 @@ function processChildContext(
184 type: any,
185 parentContext: Object,
186 ): Object {
154 - const instance = fiber.stateNode;
155 - const childContextTypes = type.childContextTypes;
187 + if (disableLegacyContext) {
188 + return parentContext;
189 + } else {
190 + const instance = fiber.stateNode;
191 + const childContextTypes = type.childContextTypes;
192 +
193 + // TODO (bvaughn) Replace this behavior with an invariant() in the future.
194 + // It has only been added in Fiber to match the (unintentional) behavior in Stack.
195 + if (typeof instance.getChildContext !== 'function') {
196 + if (__DEV__) {
197 + const componentName = getComponentName(type) || 'Unknown';
198 +
199 + if (!warnedAboutMissingGetChildContext[componentName]) {
200 + warnedAboutMissingGetChildContext[componentName] = true;
201 + warningWithoutStack(
202 + false,
203 + '%s.childContextTypes is specified but there is no getChildContext() method ' +
204 + 'on the instance. You can either define getChildContext() on %s or remove ' +
205 + 'childContextTypes from it.',
206 + componentName,
207 + componentName,
208 + );
209 + }
210 + }
211 + return parentContext;
212 + }
213
157 - // TODO (bvaughn) Replace this behavior with an invariant() in the future.
158 - // It has only been added in Fiber to match the (unintentional) behavior in Stack.
159 - if (typeof instance.getChildContext !== 'function') {
214 + let childContext;
215 if (__DEV__) {
161 - const componentName = getComponentName(type) || 'Unknown';
162 -
163 - if (!warnedAboutMissingGetChildContext[componentName]) {
164 - warnedAboutMissingGetChildContext[componentName] = true;
165 - warningWithoutStack(
166 - false,
167 - '%s.childContextTypes is specified but there is no getChildContext() method ' +
168 - 'on the instance. You can either define getChildContext() on %s or remove ' +
169 - 'childContextTypes from it.',
170 - componentName,
171 - componentName,
172 - );
173 - }
216 + setCurrentPhase('getChildContext');
217 + }
218 + startPhaseTimer(fiber, 'getChildContext');
219 + childContext = instance.getChildContext();
220 + stopPhaseTimer();
221 + if (__DEV__) {
222 + setCurrentPhase(null);
223 + }
224 + for (let contextKey in childContext) {
225 + invariant(
226 + contextKey in childContextTypes,
227 + '%s.getChildContext(): key "%s" is not defined in childContextTypes.',
228 + getComponentName(type) || 'Unknown',
229 + contextKey,
230 + );
231 + }
232 + if (__DEV__) {
233 + const name = getComponentName(type) || 'Unknown';
234 + checkPropTypes(
235 + childContextTypes,
236 + childContext,
237 + 'child context',
238 + name,
239 + // In practice, there is one case in which we won't get a stack. It's when
240 + // somebody calls unstable_renderSubtreeIntoContainer() and we process
241 + // context from the parent component instance. The stack will be missing
242 + // because it's outside of the reconciliation, and so the pointer has not
243 + // been set. This is rare and doesn't matter. We'll also remove that API.
244 + getCurrentFiberStackInDev,
245 + );
246 }
175 - return parentContext;
176 - }
247
178 - let childContext;
179 - if (__DEV__) {
180 - setCurrentPhase('getChildContext');
248 + return {...parentContext, ...childContext};
249 }
182 - startPhaseTimer(fiber, 'getChildContext');
183 - childContext = instance.getChildContext();
184 - stopPhaseTimer();
185 - if (__DEV__) {
186 - setCurrentPhase(null);
187 - }
188 - for (let contextKey in childContext) {
189 - invariant(
190 - contextKey in childContextTypes,
191 - '%s.getChildContext(): key "%s" is not defined in childContextTypes.',
192 - getComponentName(type) || 'Unknown',
193 - contextKey,
194 - );
195 - }
196 - if (__DEV__) {
197 - const name = getComponentName(type) || 'Unknown';
198 - checkPropTypes(
199 - childContextTypes,
200 - childContext,
201 - 'child context',
202 - name,
203 - // In practice, there is one case in which we won't get a stack. It's when
204 - // somebody calls unstable_renderSubtreeIntoContainer() and we process
205 - // context from the parent component instance. The stack will be missing
206 - // because it's outside of the reconciliation, and so the pointer has not
207 - // been set. This is rare and doesn't matter. We'll also remove that API.
208 - getCurrentFiberStackInDev,
209 - );
210 - }
211 -
212 - return {...parentContext, ...childContext};
250 }
251
252 function pushContextProvider(workInProgress: Fiber): boolean {
216 - const instance = workInProgress.stateNode;
217 - // We push the context as early as possible to ensure stack integrity.
218 - // If the instance does not exist yet, we will push null at first,
219 - // and replace it on the stack later when invalidating the context.
220 - const memoizedMergedChildContext =
221 - (instance && instance.__reactInternalMemoizedMergedChildContext) ||
222 - emptyContextObject;
223 -
224 - // Remember the parent context so we can merge with it later.
225 - // Inherit the parent's did-perform-work value to avoid inadvertently blocking updates.
226 - previousContext = contextStackCursor.current;
227 - push(contextStackCursor, memoizedMergedChildContext, workInProgress);
228 - push(
229 - didPerformWorkStackCursor,
230 - didPerformWorkStackCursor.current,
231 - workInProgress,
232 - );
233 -
234 - return true;
253 + if (disableLegacyContext) {
254 + return false;
255 + } else {
256 + const instance = workInProgress.stateNode;
257 + // We push the context as early as possible to ensure stack integrity.
258 + // If the instance does not exist yet, we will push null at first,
259 + // and replace it on the stack later when invalidating the context.
260 + const memoizedMergedChildContext =
261 + (instance && instance.__reactInternalMemoizedMergedChildContext) ||
262 + emptyContextObject;
263 +
264 + // Remember the parent context so we can merge with it later.
265 + // Inherit the parent's did-perform-work value to avoid inadvertently blocking updates.
266 + previousContext = contextStackCursor.current;
267 + push(contextStackCursor, memoizedMergedChildContext, workInProgress);
268 + push(
269 + didPerformWorkStackCursor,
270 + didPerformWorkStackCursor.current,
271 + workInProgress,
272 + );
273 +
274 + return true;
275 + }
276 }
277
278 function invalidateContextProvider(
@@ -239,66 +280,74 @@ function invalidateContextProvider(
280 type: any,
281 didChange: boolean,
282 ): void {
242 - const instance = workInProgress.stateNode;
243 - invariant(
244 - instance,
245 - 'Expected to have an instance by this point. ' +
246 - 'This error is likely caused by a bug in React. Please file an issue.',
247 - );
248 -
249 - if (didChange) {
250 - // Merge parent and own context.
251 - // Skip this if we're not updating due to sCU.
252 - // This avoids unnecessarily recomputing memoized values.
253 - const mergedContext = processChildContext(
254 - workInProgress,
255 - type,
256 - previousContext,
257 - );
258 - instance.__reactInternalMemoizedMergedChildContext = mergedContext;
259 -
260 - // Replace the old (or empty) context with the new one.
261 - // It is important to unwind the context in the reverse order.
262 - pop(didPerformWorkStackCursor, workInProgress);
263 - pop(contextStackCursor, workInProgress);
264 - // Now push the new context and mark that it has changed.
265 - push(contextStackCursor, mergedContext, workInProgress);
266 - push(didPerformWorkStackCursor, didChange, workInProgress);
283 + if (disableLegacyContext) {
284 + return;
285 } else {
268 - pop(didPerformWorkStackCursor, workInProgress);
269 - push(didPerformWorkStackCursor, didChange, workInProgress);
286 + const instance = workInProgress.stateNode;
287 + invariant(
288 + instance,
289 + 'Expected to have an instance by this point. ' +
290 + 'This error is likely caused by a bug in React. Please file an issue.',
291 + );
292 +
293 + if (didChange) {
294 + // Merge parent and own context.
295 + // Skip this if we're not updating due to sCU.
296 + // This avoids unnecessarily recomputing memoized values.
297 + const mergedContext = processChildContext(
298 + workInProgress,
299 + type,
300 + previousContext,
301 + );
302 + instance.__reactInternalMemoizedMergedChildContext = mergedContext;
303 +
304 + // Replace the old (or empty) context with the new one.
305 + // It is important to unwind the context in the reverse order.
306 + pop(didPerformWorkStackCursor, workInProgress);
307 + pop(contextStackCursor, workInProgress);
308 + // Now push the new context and mark that it has changed.
309 + push(contextStackCursor, mergedContext, workInProgress);
310 + push(didPerformWorkStackCursor, didChange, workInProgress);
311 + } else {
312 + pop(didPerformWorkStackCursor, workInProgress);
313 + push(didPerformWorkStackCursor, didChange, workInProgress);
314 + }
315 }
316 }
317
318 function findCurrentUnmaskedContext(fiber: Fiber): Object {
274 - // Currently this is only used with renderSubtreeIntoContainer; not sure if it
275 - // makes sense elsewhere
276 - invariant(
277 - isFiberMounted(fiber) && fiber.tag === ClassComponent,
278 - 'Expected subtree parent to be a mounted class component. ' +
279 - 'This error is likely caused by a bug in React. Please file an issue.',
280 - );
281 -
282 - let node = fiber;
283 - do {
284 - switch (node.tag) {
285 - case HostRoot:
286 - return node.stateNode.context;
287 - case ClassComponent: {
288 - const Component = node.type;
289 - if (isContextProvider(Component)) {
290 - return node.stateNode.__reactInternalMemoizedMergedChildContext;
319 + if (disableLegacyContext) {
320 + return emptyContextObject;
321 + } else {
322 + // Currently this is only used with renderSubtreeIntoContainer; not sure if it
323 + // makes sense elsewhere
324 + invariant(
325 + isFiberMounted(fiber) && fiber.tag === ClassComponent,
326 + 'Expected subtree parent to be a mounted class component. ' +
327 + 'This error is likely caused by a bug in React. Please file an issue.',
328 + );
329 +
330 + let node = fiber;
331 + do {
332 + switch (node.tag) {
333 + case HostRoot:
334 + return node.stateNode.context;
335 + case ClassComponent: {
336 + const Component = node.type;
337 + if (isContextProvider(Component)) {
338 + return node.stateNode.__reactInternalMemoizedMergedChildContext;
339 + }
340 + break;
341 }
292 - break;
342 }
294 - }
295 - node = node.return;
296 - } while (node !== null);
297 - invariant(
298 - false,
299 - 'Found unexpected detached subtree parent. ' +
300 - 'This error is likely caused by a bug in React. Please file an issue.',
301 - );
343 + node = node.return;
344 + } while (node !== null);
345 + invariant(
346 + false,
347 + 'Found unexpected detached subtree parent. ' +
348 + 'This error is likely caused by a bug in React. Please file an issue.',
349 + );
350 + }
351 }
352
353 export {
packages/shared/ReactFeatureFlags.js
+2
@@ -92,3 +92,5 @@ export const enableSuspenseCallback = false;
92 // from React.createElement to React.jsx
93 // https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md
94 export const warnAboutDefaultPropsOnFunctionComponents = false;
95 +
96 +export const disableLegacyContext = false;
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -40,6 +40,7 @@ export const flushSuspenseFallbacksInTests = true;
40 export const enableUserBlockingEvents = false;
41 export const enableSuspenseCallback = false;
42 export const warnAboutDefaultPropsOnFunctionComponents = false;
43 +export const disableLegacyContext = false;
44
45 // Only used in www builds.
46 export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -35,6 +35,7 @@ export const flushSuspenseFallbacksInTests = true;
35 export const enableUserBlockingEvents = false;
36 export const enableSuspenseCallback = false;
37 export const warnAboutDefaultPropsOnFunctionComponents = false;
38 +export const disableLegacyContext = false;
39
40 // Only used in www builds.
41 export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.persistent.js
+1
@@ -35,6 +35,7 @@ export const flushSuspenseFallbacksInTests = true;
35 export const enableUserBlockingEvents = false;
36 export const enableSuspenseCallback = false;
37 export const warnAboutDefaultPropsOnFunctionComponents = false;
38 +export const disableLegacyContext = false;
39
40 // Only used in www builds.
41 export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -35,6 +35,7 @@ export const flushSuspenseFallbacksInTests = true;
35 export const enableUserBlockingEvents = false;
36 export const enableSuspenseCallback = false;
37 export const warnAboutDefaultPropsOnFunctionComponents = false;
38 +export const disableLegacyContext = false;
39
40 // Only used in www builds.
41 export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -35,6 +35,7 @@ export const flushSuspenseFallbacksInTests = true;
35 export const enableUserBlockingEvents = false;
36 export const enableSuspenseCallback = true;
37 export const warnAboutDefaultPropsOnFunctionComponents = false;
38 +export const disableLegacyContext = false;
39
40 // Only used in www builds.
41 export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.www.js
+1
@@ -21,6 +21,7 @@ export const {
21 warnAboutDeprecatedSetNativeProps,
22 revertPassiveEffectsChange,
23 enableUserBlockingEvents,
24 + disableLegacyContext,
25 } = require('ReactFeatureFlags');
26
27 // In www, we have experimental support for gathering data