diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationLegacyContextDisabled-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationLegacyContextDisabled-test.internal.js
new file mode 100644
index 0000000000000..e8aec9ddbacbc
--- /dev/null
+++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationLegacyContextDisabled-test.internal.js
@@ -0,0 +1,168 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @emails react-core
+ */
+
+'use strict';
+
+const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
+
+let React;
+let ReactDOM;
+let ReactFeatureFlags;
+let ReactDOMServer;
+let ReactTestUtils;
+
+function initModules() {
+ // Reset warning cache.
+ jest.resetModuleRegistry();
+ React = require('react');
+ ReactDOM = require('react-dom');
+ ReactDOMServer = require('react-dom/server');
+ ReactTestUtils = require('react-dom/test-utils');
+
+ ReactFeatureFlags = require('shared/ReactFeatureFlags');
+ ReactFeatureFlags.disableLegacyContext = true;
+
+ // Make them available to the helpers.
+ return {
+ ReactDOM,
+ ReactDOMServer,
+ ReactTestUtils,
+ };
+}
+
+const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules);
+
+function formatValue(val) {
+ if (val === null) {
+ return 'null';
+ }
+ if (val === undefined) {
+ return 'undefined';
+ }
+ if (typeof val === 'string') {
+ return val;
+ }
+ return JSON.stringify(val);
+}
+
+describe('ReactDOMServerIntegrationLegacyContextDisabled', () => {
+ beforeEach(() => {
+ resetModules();
+ });
+
+ itRenders('undefined legacy context with warning', async render => {
+ class LegacyProvider extends React.Component {
+ static childContextTypes = {
+ foo() {},
+ };
+ getChildContext() {
+ return {foo: 10};
+ }
+ render() {
+ return this.props.children;
+ }
+ }
+
+ let lifecycleContextLog = [];
+ class LegacyClsConsumer extends React.Component {
+ static contextTypes = {
+ foo() {},
+ };
+ shouldComponentUpdate(nextProps, nextState, nextContext) {
+ lifecycleContextLog.push(nextContext);
+ return true;
+ }
+ UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
+ lifecycleContextLog.push(nextContext);
+ }
+ UNSAFE_componentWillUpdate(nextProps, nextState, nextContext) {
+ lifecycleContextLog.push(nextContext);
+ }
+ render() {
+ return formatValue(this.context);
+ }
+ }
+
+ function LegacyFnConsumer(props, context) {
+ return formatValue(context);
+ }
+ LegacyFnConsumer.contextTypes = {foo() {}};
+
+ function RegularFn(props, context) {
+ return formatValue(context);
+ }
+
+ const e = await render(
+
+
+
+
+
+
+ ,
+ 3,
+ );
+ expect(e.textContent).toBe('{}undefinedundefined');
+ expect(lifecycleContextLog).toEqual([]);
+ });
+
+ itRenders('modern context', async render => {
+ let Ctx = React.createContext();
+
+ class Provider extends React.Component {
+ render() {
+ return (
+
+ {this.props.children}
+
+ );
+ }
+ }
+
+ class RenderPropConsumer extends React.Component {
+ render() {
+ return {value => formatValue(value)};
+ }
+ }
+
+ let lifecycleContextLog = [];
+ class ContextTypeConsumer extends React.Component {
+ static contextType = Ctx;
+ shouldComponentUpdate(nextProps, nextState, nextContext) {
+ lifecycleContextLog.push(nextContext);
+ return true;
+ }
+ UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
+ lifecycleContextLog.push(nextContext);
+ }
+ UNSAFE_componentWillUpdate(nextProps, nextState, nextContext) {
+ lifecycleContextLog.push(nextContext);
+ }
+ render() {
+ return formatValue(this.context);
+ }
+ }
+
+ function FnConsumer() {
+ return formatValue(React.useContext(Ctx));
+ }
+
+ const e = await render(
+
+
+
+
+
+
+ ,
+ );
+ expect(e.textContent).toBe('aaa');
+ expect(lifecycleContextLog).toEqual([]);
+ });
+});
diff --git a/packages/react-dom/src/__tests__/ReactLegacyContextDisabled-test.internal.js b/packages/react-dom/src/__tests__/ReactLegacyContextDisabled-test.internal.js
new file mode 100644
index 0000000000000..f788748aa909d
--- /dev/null
+++ b/packages/react-dom/src/__tests__/ReactLegacyContextDisabled-test.internal.js
@@ -0,0 +1,207 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @emails react-core
+ */
+
+'use strict';
+
+let React;
+let ReactDOM;
+let ReactFeatureFlags;
+
+describe('ReactLegacyContextDisabled', () => {
+ beforeEach(() => {
+ jest.resetModules();
+
+ React = require('react');
+ ReactDOM = require('react-dom');
+ ReactFeatureFlags = require('shared/ReactFeatureFlags');
+ ReactFeatureFlags.disableLegacyContext = true;
+ });
+
+ function formatValue(val) {
+ if (val === null) {
+ return 'null';
+ }
+ if (val === undefined) {
+ return 'undefined';
+ }
+ if (typeof val === 'string') {
+ return val;
+ }
+ return JSON.stringify(val);
+ }
+
+ it('warns for legacy context', () => {
+ class LegacyProvider extends React.Component {
+ static childContextTypes = {
+ foo() {},
+ };
+ getChildContext() {
+ return {foo: 10};
+ }
+ render() {
+ return this.props.children;
+ }
+ }
+
+ let lifecycleContextLog = [];
+ class LegacyClsConsumer extends React.Component {
+ static contextTypes = {
+ foo() {},
+ };
+ shouldComponentUpdate(nextProps, nextState, nextContext) {
+ lifecycleContextLog.push(nextContext);
+ return true;
+ }
+ UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
+ lifecycleContextLog.push(nextContext);
+ }
+ UNSAFE_componentWillUpdate(nextProps, nextState, nextContext) {
+ lifecycleContextLog.push(nextContext);
+ }
+ render() {
+ return formatValue(this.context);
+ }
+ }
+
+ function LegacyFnConsumer(props, context) {
+ return formatValue(context);
+ }
+ LegacyFnConsumer.contextTypes = {foo() {}};
+
+ function RegularFn(props, context) {
+ return formatValue(context);
+ }
+
+ const container = document.createElement('div');
+ expect(() => {
+ ReactDOM.render(
+
+
+
+
+
+
+ ,
+ container,
+ );
+ }).toWarnDev(
+ [
+ 'LegacyProvider uses the legacy childContextTypes API which is no longer supported. ' +
+ 'Use React.createContext() instead.',
+ 'LegacyClsConsumer uses the legacy contextTypes API which is no longer supported. ' +
+ 'Use React.createContext() with static contextType instead.',
+ 'LegacyFnConsumer uses the legacy contextTypes API which is no longer supported. ' +
+ 'Use React.createContext() with React.useContext() instead.',
+ ],
+ {withoutStack: true},
+ );
+ expect(container.textContent).toBe('{}undefinedundefined');
+ expect(lifecycleContextLog).toEqual([]);
+
+ // Test update path.
+ ReactDOM.render(
+
+
+
+
+
+
+ ,
+ container,
+ );
+ expect(container.textContent).toBe('{}undefinedundefined');
+ expect(lifecycleContextLog).toEqual([{}, {}, {}]);
+ ReactDOM.unmountComponentAtNode(container);
+ });
+
+ it('renders a tree with modern context', () => {
+ let Ctx = React.createContext();
+
+ class Provider extends React.Component {
+ render() {
+ return (
+
+ {this.props.children}
+
+ );
+ }
+ }
+
+ class RenderPropConsumer extends React.Component {
+ render() {
+ return {value => formatValue(value)};
+ }
+ }
+
+ let lifecycleContextLog = [];
+ class ContextTypeConsumer extends React.Component {
+ static contextType = Ctx;
+ shouldComponentUpdate(nextProps, nextState, nextContext) {
+ lifecycleContextLog.push(nextContext);
+ return true;
+ }
+ UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
+ lifecycleContextLog.push(nextContext);
+ }
+ UNSAFE_componentWillUpdate(nextProps, nextState, nextContext) {
+ lifecycleContextLog.push(nextContext);
+ }
+ render() {
+ return formatValue(this.context);
+ }
+ }
+
+ function FnConsumer() {
+ return formatValue(React.useContext(Ctx));
+ }
+
+ const container = document.createElement('div');
+ ReactDOM.render(
+
+
+
+
+
+
+ ,
+ container,
+ );
+ expect(container.textContent).toBe('aaa');
+ expect(lifecycleContextLog).toEqual([]);
+
+ // Test update path
+ ReactDOM.render(
+
+
+
+
+
+
+ ,
+ container,
+ );
+ expect(container.textContent).toBe('aaa');
+ expect(lifecycleContextLog).toEqual(['a', 'a', 'a']);
+ lifecycleContextLog.length = 0;
+
+ ReactDOM.render(
+
+
+
+
+
+
+ ,
+ container,
+ );
+ expect(container.textContent).toBe('bbb');
+ expect(lifecycleContextLog).toEqual(['b', 'b']); // sCU skipped due to changed context value.
+ ReactDOM.unmountComponentAtNode(container);
+ });
+});
diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js
index 718108abbbf94..08c76c72aad7d 100644
--- a/packages/react-dom/src/server/ReactPartialRenderer.js
+++ b/packages/react-dom/src/server/ReactPartialRenderer.js
@@ -21,6 +21,7 @@ import describeComponentFrame from 'shared/describeComponentFrame';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {
warnAboutDeprecatedLifecycles,
+ disableLegacyContext,
enableSuspenseServerRenderer,
enableFundamentalAPI,
enableFlareAPI,
@@ -430,7 +431,8 @@ function resolve(
// Extra closure so queue and replace can be captured properly
function processChild(element, Component) {
- let publicContext = processContext(Component, context, threadID);
+ const isClass = shouldConstruct(Component);
+ const publicContext = processContext(Component, context, threadID, isClass);
let queue = [];
let replace = false;
@@ -458,7 +460,7 @@ function resolve(
};
let inst;
- if (shouldConstruct(Component)) {
+ if (isClass) {
inst = new Component(element.props, publicContext, updater);
if (typeof Component.getDerivedStateFromProps === 'function') {
@@ -650,29 +652,43 @@ function resolve(
validateRenderResult(child, Component);
let childContext;
- if (typeof inst.getChildContext === 'function') {
- let childContextTypes = Component.childContextTypes;
- if (typeof childContextTypes === 'object') {
- childContext = inst.getChildContext();
- for (let contextKey in childContext) {
- invariant(
- contextKey in childContextTypes,
- '%s.getChildContext(): key "%s" is not defined in childContextTypes.',
+ if (disableLegacyContext) {
+ if (__DEV__) {
+ let childContextTypes = Component.childContextTypes;
+ if (childContextTypes !== undefined) {
+ warningWithoutStack(
+ false,
+ '%s uses the legacy childContextTypes API which is no longer supported. ' +
+ 'Use React.createContext() instead.',
getComponentName(Component) || 'Unknown',
- contextKey,
);
}
- } else {
- warningWithoutStack(
- false,
- '%s.getChildContext(): childContextTypes must be defined in order to ' +
- 'use getChildContext().',
- getComponentName(Component) || 'Unknown',
- );
}
- }
- if (childContext) {
- context = Object.assign({}, context, childContext);
+ } else {
+ if (typeof inst.getChildContext === 'function') {
+ let childContextTypes = Component.childContextTypes;
+ if (typeof childContextTypes === 'object') {
+ childContext = inst.getChildContext();
+ for (let contextKey in childContext) {
+ invariant(
+ contextKey in childContextTypes,
+ '%s.getChildContext(): key "%s" is not defined in childContextTypes.',
+ getComponentName(Component) || 'Unknown',
+ contextKey,
+ );
+ }
+ } else {
+ warningWithoutStack(
+ false,
+ '%s.getChildContext(): childContextTypes must be defined in order to ' +
+ 'use getChildContext().',
+ getComponentName(Component) || 'Unknown',
+ );
+ }
+ }
+ if (childContext) {
+ context = Object.assign({}, context, childContext);
+ }
}
}
return {child, context};
diff --git a/packages/react-dom/src/server/ReactPartialRendererContext.js b/packages/react-dom/src/server/ReactPartialRendererContext.js
index 2ad914eb2b1ad..0caeed21251fb 100644
--- a/packages/react-dom/src/server/ReactPartialRendererContext.js
+++ b/packages/react-dom/src/server/ReactPartialRendererContext.js
@@ -10,6 +10,7 @@
import type {ThreadID} from './ReactThreadIDAllocator';
import type {ReactContext} from 'shared/ReactTypes';
+import {disableLegacyContext} from 'shared/ReactFeatureFlags';
import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import getComponentName from 'shared/getComponentName';
@@ -73,60 +74,100 @@ export function processContext(
type: Function,
context: Object,
threadID: ThreadID,
+ isClass: boolean,
) {
- const contextType = type.contextType;
- if (__DEV__) {
- if ('contextType' in (type: any)) {
- let isValid =
- // Allow null for conditional declaration
- contextType === null ||
- (contextType !== undefined &&
- contextType.$$typeof === REACT_CONTEXT_TYPE &&
- contextType._context === undefined); // Not a
+ if (isClass) {
+ const contextType = type.contextType;
+ if (__DEV__) {
+ if ('contextType' in (type: any)) {
+ let isValid =
+ // Allow null for conditional declaration
+ contextType === null ||
+ (contextType !== undefined &&
+ contextType.$$typeof === REACT_CONTEXT_TYPE &&
+ contextType._context === undefined); // Not a
- if (!isValid && !didWarnAboutInvalidateContextType.has(type)) {
- didWarnAboutInvalidateContextType.add(type);
+ if (!isValid && !didWarnAboutInvalidateContextType.has(type)) {
+ didWarnAboutInvalidateContextType.add(type);
- let addendum = '';
- if (contextType === undefined) {
- addendum =
- ' However, it is set to undefined. ' +
- 'This can be caused by a typo or by mixing up named and default imports. ' +
- 'This can also happen due to a circular dependency, so ' +
- 'try moving the createContext() call to a separate file.';
- } else if (typeof contextType !== 'object') {
- addendum = ' However, it is set to a ' + typeof contextType + '.';
- } else if (contextType.$$typeof === REACT_PROVIDER_TYPE) {
- addendum = ' Did you accidentally pass the Context.Provider instead?';
- } else if (contextType._context !== undefined) {
- //
- addendum = ' Did you accidentally pass the Context.Consumer instead?';
- } else {
- addendum =
- ' However, it is set to an object with keys {' +
- Object.keys(contextType).join(', ') +
- '}.';
+ let addendum = '';
+ if (contextType === undefined) {
+ addendum =
+ ' However, it is set to undefined. ' +
+ 'This can be caused by a typo or by mixing up named and default imports. ' +
+ 'This can also happen due to a circular dependency, so ' +
+ 'try moving the createContext() call to a separate file.';
+ } else if (typeof contextType !== 'object') {
+ addendum = ' However, it is set to a ' + typeof contextType + '.';
+ } else if (contextType.$$typeof === REACT_PROVIDER_TYPE) {
+ addendum =
+ ' Did you accidentally pass the Context.Provider instead?';
+ } else if (contextType._context !== undefined) {
+ //
+ addendum =
+ ' Did you accidentally pass the Context.Consumer instead?';
+ } else {
+ addendum =
+ ' However, it is set to an object with keys {' +
+ Object.keys(contextType).join(', ') +
+ '}.';
+ }
+ warningWithoutStack(
+ false,
+ '%s defines an invalid contextType. ' +
+ 'contextType should point to the Context object returned by React.createContext().%s',
+ getComponentName(type) || 'Component',
+ addendum,
+ );
}
- warningWithoutStack(
- false,
- '%s defines an invalid contextType. ' +
- 'contextType should point to the Context object returned by React.createContext().%s',
- getComponentName(type) || 'Component',
- addendum,
- );
}
}
- }
- if (typeof contextType === 'object' && contextType !== null) {
- validateContextBounds(contextType, threadID);
- return contextType[threadID];
+ if (typeof contextType === 'object' && contextType !== null) {
+ validateContextBounds(contextType, threadID);
+ return contextType[threadID];
+ }
+ if (disableLegacyContext) {
+ if (__DEV__) {
+ if (type.contextTypes) {
+ warningWithoutStack(
+ false,
+ '%s uses the legacy contextTypes API which is no longer supported. ' +
+ 'Use React.createContext() with static contextType instead.',
+ getComponentName(type) || 'Unknown',
+ );
+ }
+ }
+ return emptyObject;
+ } else {
+ const maskedContext = maskContext(type, context);
+ if (__DEV__) {
+ if (type.contextTypes) {
+ checkContextTypes(type.contextTypes, maskedContext, 'context');
+ }
+ }
+ return maskedContext;
+ }
} else {
- const maskedContext = maskContext(type, context);
- if (__DEV__) {
- if (type.contextTypes) {
- checkContextTypes(type.contextTypes, maskedContext, 'context');
+ if (disableLegacyContext) {
+ if (__DEV__) {
+ if (type.contextTypes) {
+ warningWithoutStack(
+ false,
+ '%s uses the legacy contextTypes API which is no longer supported. ' +
+ 'Use React.createContext() with React.useContext() instead.',
+ getComponentName(type) || 'Unknown',
+ );
+ }
+ }
+ return undefined;
+ } else {
+ const maskedContext = maskContext(type, context);
+ if (__DEV__) {
+ if (type.contextTypes) {
+ checkContextTypes(type.contextTypes, maskedContext, 'context');
+ }
}
+ return maskedContext;
}
- return maskedContext;
}
}
diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js
index c3b0930a62ccd..83b7f08538b7f 100644
--- a/packages/react-reconciler/src/ReactFiberBeginWork.js
+++ b/packages/react-reconciler/src/ReactFiberBeginWork.js
@@ -57,6 +57,7 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
import {
debugRenderPhaseSideEffects,
debugRenderPhaseSideEffectsForStrictMode,
+ disableLegacyContext,
enableProfilerTimer,
enableSchedulerTracing,
enableSuspenseServerRenderer,
@@ -615,8 +616,11 @@ function updateFunctionComponent(
}
}
- const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
- const context = getMaskedContext(workInProgress, unmaskedContext);
+ let context;
+ if (!disableLegacyContext) {
+ const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
+ context = getMaskedContext(workInProgress, unmaskedContext);
+ }
let nextChildren;
prepareToReadContext(workInProgress, renderExpirationTime);
@@ -1230,8 +1234,15 @@ function mountIndeterminateComponent(
}
const props = workInProgress.pendingProps;
- const unmaskedContext = getUnmaskedContext(workInProgress, Component, false);
- const context = getMaskedContext(workInProgress, unmaskedContext);
+ let context;
+ if (!disableLegacyContext) {
+ const unmaskedContext = getUnmaskedContext(
+ workInProgress,
+ Component,
+ false,
+ );
+ context = getMaskedContext(workInProgress, unmaskedContext);
+ }
prepareToReadContext(workInProgress, renderExpirationTime);
let value;
@@ -1349,6 +1360,15 @@ function mountIndeterminateComponent(
// Proceed under the assumption that this is a function component
workInProgress.tag = FunctionComponent;
if (__DEV__) {
+ if (disableLegacyContext && Component.contextTypes) {
+ warningWithoutStack(
+ false,
+ '%s uses the legacy contextTypes API which is no longer supported. ' +
+ 'Use React.createContext() with React.useContext() instead.',
+ getComponentName(Component) || 'Unknown',
+ );
+ }
+
if (
debugRenderPhaseSideEffects ||
(debugRenderPhaseSideEffectsForStrictMode &&
diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.js b/packages/react-reconciler/src/ReactFiberClassComponent.js
index fcadf0a22ce80..7ccc16023eaec 100644
--- a/packages/react-reconciler/src/ReactFiberClassComponent.js
+++ b/packages/react-reconciler/src/ReactFiberClassComponent.js
@@ -15,6 +15,7 @@ import {Update, Snapshot} from 'shared/ReactSideEffectTags';
import {
debugRenderPhaseSideEffects,
debugRenderPhaseSideEffectsForStrictMode,
+ disableLegacyContext,
warnAboutDeprecatedLifecycles,
} from 'shared/ReactFeatureFlags';
import ReactStrictModeWarnings from './ReactStrictModeWarnings';
@@ -361,26 +362,46 @@ function checkClassInstance(workInProgress: Fiber, ctor: any, newProps: any) {
'property to define contextType instead.',
name,
);
- const noInstanceContextTypes = !instance.contextTypes;
- warningWithoutStack(
- noInstanceContextTypes,
- 'contextTypes was defined as an instance property on %s. Use a static ' +
- 'property to define contextTypes instead.',
- name,
- );
- if (
- ctor.contextType &&
- ctor.contextTypes &&
- !didWarnAboutContextTypeAndContextTypes.has(ctor)
- ) {
- didWarnAboutContextTypeAndContextTypes.add(ctor);
+ if (disableLegacyContext) {
+ if (ctor.childContextTypes) {
+ warningWithoutStack(
+ false,
+ '%s uses the legacy childContextTypes API which is no longer supported. ' +
+ 'Use React.createContext() instead.',
+ name,
+ );
+ }
+ if (ctor.contextTypes) {
+ warningWithoutStack(
+ false,
+ '%s uses the legacy contextTypes API which is no longer supported. ' +
+ 'Use React.createContext() with static contextType instead.',
+ name,
+ );
+ }
+ } else {
+ const noInstanceContextTypes = !instance.contextTypes;
warningWithoutStack(
- false,
- '%s declares both contextTypes and contextType static properties. ' +
- 'The legacy contextTypes property will be ignored.',
+ noInstanceContextTypes,
+ 'contextTypes was defined as an instance property on %s. Use a static ' +
+ 'property to define contextTypes instead.',
name,
);
+
+ if (
+ ctor.contextType &&
+ ctor.contextTypes &&
+ !didWarnAboutContextTypeAndContextTypes.has(ctor)
+ ) {
+ didWarnAboutContextTypeAndContextTypes.add(ctor);
+ warningWithoutStack(
+ false,
+ '%s declares both contextTypes and contextType static properties. ' +
+ 'The legacy contextTypes property will be ignored.',
+ name,
+ );
+ }
}
const noComponentShouldUpdate =
@@ -534,7 +555,7 @@ function constructClassInstance(
): any {
let isLegacyContextConsumer = false;
let unmaskedContext = emptyContextObject;
- let context = null;
+ let context = emptyContextObject;
const contextType = ctor.contextType;
if (__DEV__) {
@@ -582,7 +603,7 @@ function constructClassInstance(
if (typeof contextType === 'object' && contextType !== null) {
context = readContext((contextType: any));
- } else {
+ } else if (!disableLegacyContext) {
unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
const contextTypes = ctor.contextTypes;
isLegacyContextConsumer =
@@ -785,6 +806,8 @@ function mountClassInstance(
const contextType = ctor.contextType;
if (typeof contextType === 'object' && contextType !== null) {
instance.context = readContext(contextType);
+ } else if (disableLegacyContext) {
+ instance.context = emptyContextObject;
} else {
const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
instance.context = getMaskedContext(workInProgress, unmaskedContext);
@@ -885,10 +908,10 @@ function resumeMountClassInstance(
const oldContext = instance.context;
const contextType = ctor.contextType;
- let nextContext;
+ let nextContext = emptyContextObject;
if (typeof contextType === 'object' && contextType !== null) {
nextContext = readContext(contextType);
- } else {
+ } else if (!disableLegacyContext) {
const nextLegacyUnmaskedContext = getUnmaskedContext(
workInProgress,
ctor,
@@ -1034,10 +1057,10 @@ function updateClassInstance(
const oldContext = instance.context;
const contextType = ctor.contextType;
- let nextContext;
+ let nextContext = emptyContextObject;
if (typeof contextType === 'object' && contextType !== null) {
nextContext = readContext(contextType);
- } else {
+ } else if (!disableLegacyContext) {
const nextUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
nextContext = getMaskedContext(workInProgress, nextUnmaskedContext);
}
diff --git a/packages/react-reconciler/src/ReactFiberContext.js b/packages/react-reconciler/src/ReactFiberContext.js
index 74826408ddf4a..7fd763bd5ca11 100644
--- a/packages/react-reconciler/src/ReactFiberContext.js
+++ b/packages/react-reconciler/src/ReactFiberContext.js
@@ -11,6 +11,7 @@ import type {Fiber} from './ReactFiber';
import type {StackCursor} from './ReactFiberStack';
import {isFiberMounted} from 'react-reconciler/reflection';
+import {disableLegacyContext} from 'shared/ReactFeatureFlags';
import {ClassComponent, HostRoot} from 'shared/ReactWorkTags';
import getComponentName from 'shared/getComponentName';
import invariant from 'shared/invariant';
@@ -46,14 +47,18 @@ function getUnmaskedContext(
Component: Function,
didPushOwnContextIfProvider: boolean,
): Object {
- if (didPushOwnContextIfProvider && isContextProvider(Component)) {
- // If the fiber is a context provider itself, when we read its context
- // we may have already pushed its own child context on the stack. A context
- // provider should not "see" its own child context. Therefore we read the
- // previous (parent) context instead for a context provider.
- return previousContext;
+ if (disableLegacyContext) {
+ return emptyContextObject;
+ } else {
+ if (didPushOwnContextIfProvider && isContextProvider(Component)) {
+ // If the fiber is a context provider itself, when we read its context
+ // we may have already pushed its own child context on the stack. A context
+ // provider should not "see" its own child context. Therefore we read the
+ // previous (parent) context instead for a context provider.
+ return previousContext;
+ }
+ return contextStackCursor.current;
}
- return contextStackCursor.current;
}
function cacheContext(
@@ -61,74 +66,98 @@ function cacheContext(
unmaskedContext: Object,
maskedContext: Object,
): void {
- const instance = workInProgress.stateNode;
- instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
- instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
+ if (disableLegacyContext) {
+ return;
+ } else {
+ const instance = workInProgress.stateNode;
+ instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
+ instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
+ }
}
function getMaskedContext(
workInProgress: Fiber,
unmaskedContext: Object,
): Object {
- const type = workInProgress.type;
- const contextTypes = type.contextTypes;
- if (!contextTypes) {
+ if (disableLegacyContext) {
return emptyContextObject;
- }
+ } else {
+ const type = workInProgress.type;
+ const contextTypes = type.contextTypes;
+ if (!contextTypes) {
+ return emptyContextObject;
+ }
- // Avoid recreating masked context unless unmasked context has changed.
- // Failing to do this will result in unnecessary calls to componentWillReceiveProps.
- // This may trigger infinite loops if componentWillReceiveProps calls setState.
- const instance = workInProgress.stateNode;
- if (
- instance &&
- instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
- ) {
- return instance.__reactInternalMemoizedMaskedChildContext;
- }
+ // Avoid recreating masked context unless unmasked context has changed.
+ // Failing to do this will result in unnecessary calls to componentWillReceiveProps.
+ // This may trigger infinite loops if componentWillReceiveProps calls setState.
+ const instance = workInProgress.stateNode;
+ if (
+ instance &&
+ instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
+ ) {
+ return instance.__reactInternalMemoizedMaskedChildContext;
+ }
- const context = {};
- for (let key in contextTypes) {
- context[key] = unmaskedContext[key];
- }
+ const context = {};
+ for (let key in contextTypes) {
+ context[key] = unmaskedContext[key];
+ }
- if (__DEV__) {
- const name = getComponentName(type) || 'Unknown';
- checkPropTypes(
- contextTypes,
- context,
- 'context',
- name,
- getCurrentFiberStackInDev,
- );
- }
+ if (__DEV__) {
+ const name = getComponentName(type) || 'Unknown';
+ checkPropTypes(
+ contextTypes,
+ context,
+ 'context',
+ name,
+ getCurrentFiberStackInDev,
+ );
+ }
- // Cache unmasked context so we can avoid recreating masked context unless necessary.
- // Context is created before the class component is instantiated so check for instance.
- if (instance) {
- cacheContext(workInProgress, unmaskedContext, context);
- }
+ // Cache unmasked context so we can avoid recreating masked context unless necessary.
+ // Context is created before the class component is instantiated so check for instance.
+ if (instance) {
+ cacheContext(workInProgress, unmaskedContext, context);
+ }
- return context;
+ return context;
+ }
}
function hasContextChanged(): boolean {
- return didPerformWorkStackCursor.current;
+ if (disableLegacyContext) {
+ return false;
+ } else {
+ return didPerformWorkStackCursor.current;
+ }
}
function isContextProvider(type: Function): boolean {
- const childContextTypes = type.childContextTypes;
- return childContextTypes !== null && childContextTypes !== undefined;
+ if (disableLegacyContext) {
+ return false;
+ } else {
+ const childContextTypes = type.childContextTypes;
+ return childContextTypes !== null && childContextTypes !== undefined;
+ }
}
function popContext(fiber: Fiber): void {
- pop(didPerformWorkStackCursor, fiber);
- pop(contextStackCursor, fiber);
+ if (disableLegacyContext) {
+ return;
+ } else {
+ pop(didPerformWorkStackCursor, fiber);
+ pop(contextStackCursor, fiber);
+ }
}
function popTopLevelContextObject(fiber: Fiber): void {
- pop(didPerformWorkStackCursor, fiber);
- pop(contextStackCursor, fiber);
+ if (disableLegacyContext) {
+ return;
+ } else {
+ pop(didPerformWorkStackCursor, fiber);
+ pop(contextStackCursor, fiber);
+ }
}
function pushTopLevelContextObject(
@@ -136,14 +165,18 @@ function pushTopLevelContextObject(
context: Object,
didChange: boolean,
): void {
- invariant(
- contextStackCursor.current === emptyContextObject,
- 'Unexpected context found on stack. ' +
- 'This error is likely caused by a bug in React. Please file an issue.',
- );
-
- push(contextStackCursor, context, fiber);
- push(didPerformWorkStackCursor, didChange, fiber);
+ if (disableLegacyContext) {
+ return;
+ } else {
+ invariant(
+ contextStackCursor.current === emptyContextObject,
+ 'Unexpected context found on stack. ' +
+ 'This error is likely caused by a bug in React. Please file an issue.',
+ );
+
+ push(contextStackCursor, context, fiber);
+ push(didPerformWorkStackCursor, didChange, fiber);
+ }
}
function processChildContext(
@@ -151,87 +184,95 @@ function processChildContext(
type: any,
parentContext: Object,
): Object {
- const instance = fiber.stateNode;
- const childContextTypes = type.childContextTypes;
+ if (disableLegacyContext) {
+ return parentContext;
+ } else {
+ const instance = fiber.stateNode;
+ const childContextTypes = type.childContextTypes;
+
+ // TODO (bvaughn) Replace this behavior with an invariant() in the future.
+ // It has only been added in Fiber to match the (unintentional) behavior in Stack.
+ if (typeof instance.getChildContext !== 'function') {
+ if (__DEV__) {
+ const componentName = getComponentName(type) || 'Unknown';
+
+ if (!warnedAboutMissingGetChildContext[componentName]) {
+ warnedAboutMissingGetChildContext[componentName] = true;
+ warningWithoutStack(
+ false,
+ '%s.childContextTypes is specified but there is no getChildContext() method ' +
+ 'on the instance. You can either define getChildContext() on %s or remove ' +
+ 'childContextTypes from it.',
+ componentName,
+ componentName,
+ );
+ }
+ }
+ return parentContext;
+ }
- // TODO (bvaughn) Replace this behavior with an invariant() in the future.
- // It has only been added in Fiber to match the (unintentional) behavior in Stack.
- if (typeof instance.getChildContext !== 'function') {
+ let childContext;
if (__DEV__) {
- const componentName = getComponentName(type) || 'Unknown';
-
- if (!warnedAboutMissingGetChildContext[componentName]) {
- warnedAboutMissingGetChildContext[componentName] = true;
- warningWithoutStack(
- false,
- '%s.childContextTypes is specified but there is no getChildContext() method ' +
- 'on the instance. You can either define getChildContext() on %s or remove ' +
- 'childContextTypes from it.',
- componentName,
- componentName,
- );
- }
+ setCurrentPhase('getChildContext');
+ }
+ startPhaseTimer(fiber, 'getChildContext');
+ childContext = instance.getChildContext();
+ stopPhaseTimer();
+ if (__DEV__) {
+ setCurrentPhase(null);
+ }
+ for (let contextKey in childContext) {
+ invariant(
+ contextKey in childContextTypes,
+ '%s.getChildContext(): key "%s" is not defined in childContextTypes.',
+ getComponentName(type) || 'Unknown',
+ contextKey,
+ );
+ }
+ if (__DEV__) {
+ const name = getComponentName(type) || 'Unknown';
+ checkPropTypes(
+ childContextTypes,
+ childContext,
+ 'child context',
+ name,
+ // In practice, there is one case in which we won't get a stack. It's when
+ // somebody calls unstable_renderSubtreeIntoContainer() and we process
+ // context from the parent component instance. The stack will be missing
+ // because it's outside of the reconciliation, and so the pointer has not
+ // been set. This is rare and doesn't matter. We'll also remove that API.
+ getCurrentFiberStackInDev,
+ );
}
- return parentContext;
- }
- let childContext;
- if (__DEV__) {
- setCurrentPhase('getChildContext');
+ return {...parentContext, ...childContext};
}
- startPhaseTimer(fiber, 'getChildContext');
- childContext = instance.getChildContext();
- stopPhaseTimer();
- if (__DEV__) {
- setCurrentPhase(null);
- }
- for (let contextKey in childContext) {
- invariant(
- contextKey in childContextTypes,
- '%s.getChildContext(): key "%s" is not defined in childContextTypes.',
- getComponentName(type) || 'Unknown',
- contextKey,
- );
- }
- if (__DEV__) {
- const name = getComponentName(type) || 'Unknown';
- checkPropTypes(
- childContextTypes,
- childContext,
- 'child context',
- name,
- // In practice, there is one case in which we won't get a stack. It's when
- // somebody calls unstable_renderSubtreeIntoContainer() and we process
- // context from the parent component instance. The stack will be missing
- // because it's outside of the reconciliation, and so the pointer has not
- // been set. This is rare and doesn't matter. We'll also remove that API.
- getCurrentFiberStackInDev,
- );
- }
-
- return {...parentContext, ...childContext};
}
function pushContextProvider(workInProgress: Fiber): boolean {
- const instance = workInProgress.stateNode;
- // We push the context as early as possible to ensure stack integrity.
- // If the instance does not exist yet, we will push null at first,
- // and replace it on the stack later when invalidating the context.
- const memoizedMergedChildContext =
- (instance && instance.__reactInternalMemoizedMergedChildContext) ||
- emptyContextObject;
-
- // Remember the parent context so we can merge with it later.
- // Inherit the parent's did-perform-work value to avoid inadvertently blocking updates.
- previousContext = contextStackCursor.current;
- push(contextStackCursor, memoizedMergedChildContext, workInProgress);
- push(
- didPerformWorkStackCursor,
- didPerformWorkStackCursor.current,
- workInProgress,
- );
-
- return true;
+ if (disableLegacyContext) {
+ return false;
+ } else {
+ const instance = workInProgress.stateNode;
+ // We push the context as early as possible to ensure stack integrity.
+ // If the instance does not exist yet, we will push null at first,
+ // and replace it on the stack later when invalidating the context.
+ const memoizedMergedChildContext =
+ (instance && instance.__reactInternalMemoizedMergedChildContext) ||
+ emptyContextObject;
+
+ // Remember the parent context so we can merge with it later.
+ // Inherit the parent's did-perform-work value to avoid inadvertently blocking updates.
+ previousContext = contextStackCursor.current;
+ push(contextStackCursor, memoizedMergedChildContext, workInProgress);
+ push(
+ didPerformWorkStackCursor,
+ didPerformWorkStackCursor.current,
+ workInProgress,
+ );
+
+ return true;
+ }
}
function invalidateContextProvider(
@@ -239,66 +280,74 @@ function invalidateContextProvider(
type: any,
didChange: boolean,
): void {
- const instance = workInProgress.stateNode;
- invariant(
- instance,
- 'Expected to have an instance by this point. ' +
- 'This error is likely caused by a bug in React. Please file an issue.',
- );
-
- if (didChange) {
- // Merge parent and own context.
- // Skip this if we're not updating due to sCU.
- // This avoids unnecessarily recomputing memoized values.
- const mergedContext = processChildContext(
- workInProgress,
- type,
- previousContext,
- );
- instance.__reactInternalMemoizedMergedChildContext = mergedContext;
-
- // Replace the old (or empty) context with the new one.
- // It is important to unwind the context in the reverse order.
- pop(didPerformWorkStackCursor, workInProgress);
- pop(contextStackCursor, workInProgress);
- // Now push the new context and mark that it has changed.
- push(contextStackCursor, mergedContext, workInProgress);
- push(didPerformWorkStackCursor, didChange, workInProgress);
+ if (disableLegacyContext) {
+ return;
} else {
- pop(didPerformWorkStackCursor, workInProgress);
- push(didPerformWorkStackCursor, didChange, workInProgress);
+ const instance = workInProgress.stateNode;
+ invariant(
+ instance,
+ 'Expected to have an instance by this point. ' +
+ 'This error is likely caused by a bug in React. Please file an issue.',
+ );
+
+ if (didChange) {
+ // Merge parent and own context.
+ // Skip this if we're not updating due to sCU.
+ // This avoids unnecessarily recomputing memoized values.
+ const mergedContext = processChildContext(
+ workInProgress,
+ type,
+ previousContext,
+ );
+ instance.__reactInternalMemoizedMergedChildContext = mergedContext;
+
+ // Replace the old (or empty) context with the new one.
+ // It is important to unwind the context in the reverse order.
+ pop(didPerformWorkStackCursor, workInProgress);
+ pop(contextStackCursor, workInProgress);
+ // Now push the new context and mark that it has changed.
+ push(contextStackCursor, mergedContext, workInProgress);
+ push(didPerformWorkStackCursor, didChange, workInProgress);
+ } else {
+ pop(didPerformWorkStackCursor, workInProgress);
+ push(didPerformWorkStackCursor, didChange, workInProgress);
+ }
}
}
function findCurrentUnmaskedContext(fiber: Fiber): Object {
- // Currently this is only used with renderSubtreeIntoContainer; not sure if it
- // makes sense elsewhere
- invariant(
- isFiberMounted(fiber) && fiber.tag === ClassComponent,
- 'Expected subtree parent to be a mounted class component. ' +
- 'This error is likely caused by a bug in React. Please file an issue.',
- );
-
- let node = fiber;
- do {
- switch (node.tag) {
- case HostRoot:
- return node.stateNode.context;
- case ClassComponent: {
- const Component = node.type;
- if (isContextProvider(Component)) {
- return node.stateNode.__reactInternalMemoizedMergedChildContext;
+ if (disableLegacyContext) {
+ return emptyContextObject;
+ } else {
+ // Currently this is only used with renderSubtreeIntoContainer; not sure if it
+ // makes sense elsewhere
+ invariant(
+ isFiberMounted(fiber) && fiber.tag === ClassComponent,
+ 'Expected subtree parent to be a mounted class component. ' +
+ 'This error is likely caused by a bug in React. Please file an issue.',
+ );
+
+ let node = fiber;
+ do {
+ switch (node.tag) {
+ case HostRoot:
+ return node.stateNode.context;
+ case ClassComponent: {
+ const Component = node.type;
+ if (isContextProvider(Component)) {
+ return node.stateNode.__reactInternalMemoizedMergedChildContext;
+ }
+ break;
}
- break;
}
- }
- node = node.return;
- } while (node !== null);
- invariant(
- false,
- 'Found unexpected detached subtree parent. ' +
- 'This error is likely caused by a bug in React. Please file an issue.',
- );
+ node = node.return;
+ } while (node !== null);
+ invariant(
+ false,
+ 'Found unexpected detached subtree parent. ' +
+ 'This error is likely caused by a bug in React. Please file an issue.',
+ );
+ }
}
export {
diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js
index 6dc443b3e7ed8..da01edb138783 100644
--- a/packages/shared/ReactFeatureFlags.js
+++ b/packages/shared/ReactFeatureFlags.js
@@ -92,3 +92,5 @@ export const enableSuspenseCallback = false;
// from React.createElement to React.jsx
// https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md
export const warnAboutDefaultPropsOnFunctionComponents = false;
+
+export const disableLegacyContext = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js
index 7c6783463aa59..6b95b92d207ef 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-fb.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js
@@ -40,6 +40,7 @@ export const flushSuspenseFallbacksInTests = true;
export const enableUserBlockingEvents = false;
export const enableSuspenseCallback = false;
export const warnAboutDefaultPropsOnFunctionComponents = false;
+export const disableLegacyContext = false;
// Only used in www builds.
export function addUserTimingListener() {
diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js
index 291e5d9c58b8f..08525d0c0938b 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-oss.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js
@@ -35,6 +35,7 @@ export const flushSuspenseFallbacksInTests = true;
export const enableUserBlockingEvents = false;
export const enableSuspenseCallback = false;
export const warnAboutDefaultPropsOnFunctionComponents = false;
+export const disableLegacyContext = false;
// Only used in www builds.
export function addUserTimingListener() {
diff --git a/packages/shared/forks/ReactFeatureFlags.persistent.js b/packages/shared/forks/ReactFeatureFlags.persistent.js
index e62a6f88dc768..478586ff80a04 100644
--- a/packages/shared/forks/ReactFeatureFlags.persistent.js
+++ b/packages/shared/forks/ReactFeatureFlags.persistent.js
@@ -35,6 +35,7 @@ export const flushSuspenseFallbacksInTests = true;
export const enableUserBlockingEvents = false;
export const enableSuspenseCallback = false;
export const warnAboutDefaultPropsOnFunctionComponents = false;
+export const disableLegacyContext = false;
// Only used in www builds.
export function addUserTimingListener() {
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
index 8b8013db7bd33..91bfab22efa0e 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
@@ -35,6 +35,7 @@ export const flushSuspenseFallbacksInTests = true;
export const enableUserBlockingEvents = false;
export const enableSuspenseCallback = false;
export const warnAboutDefaultPropsOnFunctionComponents = false;
+export const disableLegacyContext = false;
// Only used in www builds.
export function addUserTimingListener() {
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
index 813691c9dfb14..28f7d534292f4 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
@@ -35,6 +35,7 @@ export const flushSuspenseFallbacksInTests = true;
export const enableUserBlockingEvents = false;
export const enableSuspenseCallback = true;
export const warnAboutDefaultPropsOnFunctionComponents = false;
+export const disableLegacyContext = false;
// Only used in www builds.
export function addUserTimingListener() {
diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js
index e176657cad280..3f2c4c6b519be 100644
--- a/packages/shared/forks/ReactFeatureFlags.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.www.js
@@ -21,6 +21,7 @@ export const {
warnAboutDeprecatedSetNativeProps,
revertPassiveEffectsChange,
enableUserBlockingEvents,
+ disableLegacyContext,
} = require('ReactFeatureFlags');
// In www, we have experimental support for gathering data