diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js
index e94ae8109781a..61c75d05cddc6 100644
--- a/packages/react-reconciler/src/ReactFiberBeginWork.js
+++ b/packages/react-reconciler/src/ReactFiberBeginWork.js
@@ -59,7 +59,6 @@ import warningWithoutStack from 'shared/warningWithoutStack';
import * as ReactCurrentFiber from './ReactCurrentFiber';
import {cancelWorkTimer} from './ReactDebugFiberPerf';
-import {applyDerivedStateFromProps} from './ReactFiberClassComponent';
import {
mountChildFibers,
reconcileChildFibers,
@@ -97,6 +96,7 @@ import {
} from './ReactFiberHydrationContext';
import {
adoptClassInstance,
+ applyDerivedStateFromProps,
constructClassInstance,
mountClassInstance,
resumeMountClassInstance,
@@ -109,11 +109,13 @@ import {resolveLazyComponentTag} from './ReactFiber';
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
let didWarnAboutBadClass;
+let didWarnAboutContextTypeOnFunctionalComponent;
let didWarnAboutGetDerivedStateOnFunctionalComponent;
let didWarnAboutStatelessRefs;
if (__DEV__) {
didWarnAboutBadClass = {};
+ didWarnAboutContextTypeOnFunctionalComponent = {};
didWarnAboutGetDerivedStateOnFunctionalComponent = {};
didWarnAboutStatelessRefs = {};
}
@@ -805,6 +807,22 @@ function mountIndeterminateComponent(
] = true;
}
}
+
+ if (
+ typeof Component.contextType === 'object' &&
+ Component.contextType !== null
+ ) {
+ const componentName = getComponentName(Component) || 'Unknown';
+
+ if (!didWarnAboutContextTypeOnFunctionalComponent[componentName]) {
+ warningWithoutStack(
+ false,
+ '%s: Stateless functional components do not support contextType.',
+ componentName,
+ );
+ didWarnAboutContextTypeOnFunctionalComponent[componentName] = true;
+ }
+ }
}
reconcileChildren(current, workInProgress, value, renderExpirationTime);
memoizeProps(workInProgress, props);
diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.js b/packages/react-reconciler/src/ReactFiberClassComponent.js
index 48b29e7917f8e..ee0da92c911fc 100644
--- a/packages/react-reconciler/src/ReactFiberClassComponent.js
+++ b/packages/react-reconciler/src/ReactFiberClassComponent.js
@@ -65,6 +65,8 @@ let didWarnAboutUndefinedDerivedState;
let warnOnUndefinedDerivedState;
let warnOnInvalidCallback;
let didWarnAboutDirectlyAssigningPropsToState;
+let didWarnAboutContextTypeAndContextTypes;
+let didWarnAboutInvalidateContextType;
if (__DEV__) {
didWarnAboutStateAssignmentForComponent = new Set();
@@ -73,6 +75,8 @@ if (__DEV__) {
didWarnAboutLegacyLifecyclesAndDerivedState = new Set();
didWarnAboutDirectlyAssigningPropsToState = new Set();
didWarnAboutUndefinedDerivedState = new Set();
+ didWarnAboutContextTypeAndContextTypes = new Set();
+ didWarnAboutInvalidateContextType = new Set();
const didWarnOnInvalidCallback = new Set();
@@ -234,7 +238,7 @@ function checkShouldComponentUpdate(
newProps,
oldState,
newState,
- nextLegacyContext,
+ nextContext,
) {
const instance = workInProgress.stateNode;
if (typeof instance.shouldComponentUpdate === 'function') {
@@ -242,7 +246,7 @@ function checkShouldComponentUpdate(
const shouldUpdate = instance.shouldComponentUpdate(
newProps,
newState,
- nextLegacyContext,
+ nextContext,
);
stopPhaseTimer();
@@ -319,6 +323,13 @@ function checkClassInstance(workInProgress: Fiber, ctor: any, newProps: any) {
'property to define propTypes instead.',
name,
);
+ const noInstanceContextType = !instance.contextType;
+ warningWithoutStack(
+ noInstanceContextType,
+ 'contextType was defined as an instance property on %s. Use a static ' +
+ 'property to define contextType instead.',
+ name,
+ );
const noInstanceContextTypes = !instance.contextTypes;
warningWithoutStack(
noInstanceContextTypes,
@@ -326,6 +337,36 @@ function checkClassInstance(workInProgress: Fiber, ctor: any, newProps: any) {
'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,
+ );
+ }
+
+ if (
+ ctor.contextType &&
+ typeof ctor.contextType.unstable_read !== 'function' &&
+ !didWarnAboutInvalidateContextType.has(ctor)
+ ) {
+ didWarnAboutInvalidateContextType.add(ctor);
+ warningWithoutStack(
+ false,
+ '%s defines an invalid contextType. ' +
+ 'contextType should point to the Context object returned by React.createContext(). ' +
+ 'Did you accidentally pass the Context.Provider instead?',
+ name,
+ );
+ }
+
const noComponentShouldUpdate =
typeof instance.componentShouldUpdate !== 'function';
warningWithoutStack(
@@ -475,12 +516,25 @@ function constructClassInstance(
props: any,
renderExpirationTime: ExpirationTime,
): any {
- const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
- const contextTypes = ctor.contextTypes;
- const isContextConsumer = contextTypes !== null && contextTypes !== undefined;
- const context = isContextConsumer
- ? getMaskedContext(workInProgress, unmaskedContext)
- : emptyContextObject;
+ let isLegacyContextConsumer = false;
+ let unmaskedContext = emptyContextObject;
+ let context = null;
+ const contextType = ctor.contextType;
+ if (
+ typeof contextType === 'object' &&
+ contextType !== null &&
+ typeof contextType.unstable_read === 'function'
+ ) {
+ context = (contextType: any).unstable_read();
+ } else {
+ unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
+ const contextTypes = ctor.contextTypes;
+ isLegacyContextConsumer =
+ contextTypes !== null && contextTypes !== undefined;
+ context = isLegacyContextConsumer
+ ? getMaskedContext(workInProgress, unmaskedContext)
+ : emptyContextObject;
+ }
// Instantiate twice to help detect side-effects.
if (__DEV__) {
@@ -587,7 +641,7 @@ function constructClassInstance(
// Cache unmasked context so we can avoid recreating masked context unless necessary.
// ReactFiberContext usually updates this cache but can't for newly-created instances.
- if (isContextConsumer) {
+ if (isLegacyContextConsumer) {
cacheContext(workInProgress, unmaskedContext, context);
}
@@ -625,15 +679,15 @@ function callComponentWillReceiveProps(
workInProgress,
instance,
newProps,
- nextLegacyContext,
+ nextContext,
) {
const oldState = instance.state;
startPhaseTimer(workInProgress, 'componentWillReceiveProps');
if (typeof instance.componentWillReceiveProps === 'function') {
- instance.componentWillReceiveProps(newProps, nextLegacyContext);
+ instance.componentWillReceiveProps(newProps, nextContext);
}
if (typeof instance.UNSAFE_componentWillReceiveProps === 'function') {
- instance.UNSAFE_componentWillReceiveProps(newProps, nextLegacyContext);
+ instance.UNSAFE_componentWillReceiveProps(newProps, nextContext);
}
stopPhaseTimer();
@@ -668,12 +722,21 @@ function mountClassInstance(
}
const instance = workInProgress.stateNode;
- const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
-
instance.props = newProps;
instance.state = workInProgress.memoizedState;
instance.refs = emptyRefsObject;
- instance.context = getMaskedContext(workInProgress, unmaskedContext);
+
+ const contextType = ctor.contextType;
+ if (
+ typeof contextType === 'object' &&
+ contextType !== null &&
+ typeof contextType.unstable_read === 'function'
+ ) {
+ instance.context = (contextType: any).unstable_read();
+ } else {
+ const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
+ instance.context = getMaskedContext(workInProgress, unmaskedContext);
+ }
if (__DEV__) {
if (instance.state === newProps) {
@@ -774,15 +837,22 @@ function resumeMountClassInstance(
instance.props = oldProps;
const oldContext = instance.context;
- const nextLegacyUnmaskedContext = getUnmaskedContext(
- workInProgress,
- ctor,
- true,
- );
- const nextLegacyContext = getMaskedContext(
- workInProgress,
- nextLegacyUnmaskedContext,
- );
+ const contextType = ctor.contextType;
+ let nextContext;
+ if (
+ typeof contextType === 'object' &&
+ contextType !== null &&
+ typeof contextType.unstable_read === 'function'
+ ) {
+ nextContext = (contextType: any).unstable_read();
+ } else {
+ const nextLegacyUnmaskedContext = getUnmaskedContext(
+ workInProgress,
+ ctor,
+ true,
+ );
+ nextContext = getMaskedContext(workInProgress, nextLegacyUnmaskedContext);
+ }
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
const hasNewLifecycles =
@@ -800,12 +870,12 @@ function resumeMountClassInstance(
(typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
typeof instance.componentWillReceiveProps === 'function')
) {
- if (oldProps !== newProps || oldContext !== nextLegacyContext) {
+ if (oldProps !== newProps || oldContext !== nextContext) {
callComponentWillReceiveProps(
workInProgress,
instance,
newProps,
- nextLegacyContext,
+ nextContext,
);
}
}
@@ -858,7 +928,7 @@ function resumeMountClassInstance(
newProps,
oldState,
newState,
- nextLegacyContext,
+ nextContext,
);
if (shouldUpdate) {
@@ -898,7 +968,7 @@ function resumeMountClassInstance(
// if shouldComponentUpdate returns false.
instance.props = newProps;
instance.state = newState;
- instance.context = nextLegacyContext;
+ instance.context = nextContext;
return shouldUpdate;
}
@@ -917,15 +987,18 @@ function updateClassInstance(
instance.props = oldProps;
const oldContext = instance.context;
- const nextLegacyUnmaskedContext = getUnmaskedContext(
- workInProgress,
- ctor,
- true,
- );
- const nextLegacyContext = getMaskedContext(
- workInProgress,
- nextLegacyUnmaskedContext,
- );
+ const contextType = ctor.contextType;
+ let nextContext;
+ if (
+ typeof contextType === 'object' &&
+ contextType !== null &&
+ typeof contextType.unstable_read === 'function'
+ ) {
+ nextContext = (contextType: any).unstable_read();
+ } else {
+ const nextUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
+ nextContext = getMaskedContext(workInProgress, nextUnmaskedContext);
+ }
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
const hasNewLifecycles =
@@ -943,12 +1016,12 @@ function updateClassInstance(
(typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
typeof instance.componentWillReceiveProps === 'function')
) {
- if (oldProps !== newProps || oldContext !== nextLegacyContext) {
+ if (oldProps !== newProps || oldContext !== nextContext) {
callComponentWillReceiveProps(
workInProgress,
instance,
newProps,
- nextLegacyContext,
+ nextContext,
);
}
}
@@ -1015,7 +1088,7 @@ function updateClassInstance(
newProps,
oldState,
newState,
- nextLegacyContext,
+ nextContext,
);
if (shouldUpdate) {
@@ -1028,14 +1101,10 @@ function updateClassInstance(
) {
startPhaseTimer(workInProgress, 'componentWillUpdate');
if (typeof instance.componentWillUpdate === 'function') {
- instance.componentWillUpdate(newProps, newState, nextLegacyContext);
+ instance.componentWillUpdate(newProps, newState, nextContext);
}
if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
- instance.UNSAFE_componentWillUpdate(
- newProps,
- newState,
- nextLegacyContext,
- );
+ instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);
}
stopPhaseTimer();
}
@@ -1075,7 +1144,7 @@ function updateClassInstance(
// if shouldComponentUpdate returns false.
instance.props = newProps;
instance.state = newState;
- instance.context = nextLegacyContext;
+ instance.context = nextContext;
return shouldUpdate;
}
diff --git a/packages/react/src/__tests__/ReactCoffeeScriptClass-test.coffee b/packages/react/src/__tests__/ReactCoffeeScriptClass-test.coffee
index 88faa12d2d5a8..ec0921293d53e 100644
--- a/packages/react/src/__tests__/ReactCoffeeScriptClass-test.coffee
+++ b/packages/react/src/__tests__/ReactCoffeeScriptClass-test.coffee
@@ -392,6 +392,7 @@ describe 'ReactCoffeeScriptClass', ->
class Foo extends React.Component
constructor: ->
@contextTypes = {}
+ @contextType = {}
@propTypes = {}
getInitialState: ->
@@ -413,6 +414,7 @@ describe 'ReactCoffeeScriptClass', ->
'getDefaultProps was defined on Foo, a plain JavaScript class.',
'propTypes was defined as an instance property on Foo.',
'contextTypes was defined as an instance property on Foo.',
+ 'contextType was defined as an instance property on Foo.',
], {withoutStack: true})
expect(getInitialStateWasCalled).toBe false
expect(getDefaultPropsWasCalled).toBe false
diff --git a/packages/react/src/__tests__/ReactContextValidator-test.js b/packages/react/src/__tests__/ReactContextValidator-test.js
index be6ce254b4539..97ac4e339cbc4 100644
--- a/packages/react/src/__tests__/ReactContextValidator-test.js
+++ b/packages/react/src/__tests__/ReactContextValidator-test.js
@@ -67,9 +67,16 @@ describe('ReactContextValidator', () => {
});
it('should pass next context to lifecycles', () => {
- let actualComponentWillReceiveProps;
- let actualShouldComponentUpdate;
- let actualComponentWillUpdate;
+ let componentDidMountContext;
+ let componentDidUpdateContext;
+ let componentWillReceivePropsContext;
+ let componentWillReceivePropsNextContext;
+ let componentWillUpdateContext;
+ let componentWillUpdateNextContext;
+ let constructorContext;
+ let renderContext;
+ let shouldComponentUpdateContext;
+ let shouldComponentUpdateNextContext;
class Parent extends React.Component {
getChildContext() {
@@ -78,7 +85,6 @@ describe('ReactContextValidator', () => {
bar: 'bar',
};
}
-
render() {
return