Skip to content

Commit

Permalink
Support class component static contextType attribute (#13728)
Browse files Browse the repository at this point in the history
* Support class component static contextType attribute
  • Loading branch information
bvaughn committed Sep 25, 2018
1 parent f305d2a commit 4b68a64
Show file tree
Hide file tree
Showing 6 changed files with 382 additions and 61 deletions.
20 changes: 19 additions & 1 deletion packages/react-reconciler/src/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -97,6 +96,7 @@ import {
} from './ReactFiberHydrationContext';
import {
adoptClassInstance,
applyDerivedStateFromProps,
constructClassInstance,
mountClassInstance,
resumeMountClassInstance,
Expand All @@ -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 = {};
}
Expand Down Expand Up @@ -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);
Expand Down
163 changes: 116 additions & 47 deletions packages/react-reconciler/src/ReactFiberClassComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ let didWarnAboutUndefinedDerivedState;
let warnOnUndefinedDerivedState;
let warnOnInvalidCallback;
let didWarnAboutDirectlyAssigningPropsToState;
let didWarnAboutContextTypeAndContextTypes;
let didWarnAboutInvalidateContextType;

if (__DEV__) {
didWarnAboutStateAssignmentForComponent = new Set();
Expand All @@ -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();

Expand Down Expand Up @@ -234,15 +238,15 @@ function checkShouldComponentUpdate(
newProps,
oldState,
newState,
nextLegacyContext,
nextContext,
) {
const instance = workInProgress.stateNode;
if (typeof instance.shouldComponentUpdate === 'function') {
startPhaseTimer(workInProgress, 'shouldComponentUpdate');
const shouldUpdate = instance.shouldComponentUpdate(
newProps,
newState,
nextLegacyContext,
nextContext,
);
stopPhaseTimer();

Expand Down Expand Up @@ -319,13 +323,50 @@ 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,
'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,
);
}

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(
Expand Down Expand Up @@ -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__) {
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 =
Expand All @@ -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,
);
}
}
Expand Down Expand Up @@ -858,7 +928,7 @@ function resumeMountClassInstance(
newProps,
oldState,
newState,
nextLegacyContext,
nextContext,
);

if (shouldUpdate) {
Expand Down Expand Up @@ -898,7 +968,7 @@ function resumeMountClassInstance(
// if shouldComponentUpdate returns false.
instance.props = newProps;
instance.state = newState;
instance.context = nextLegacyContext;
instance.context = nextContext;

return shouldUpdate;
}
Expand All @@ -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 =
Expand All @@ -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,
);
}
}
Expand Down Expand Up @@ -1015,7 +1088,7 @@ function updateClassInstance(
newProps,
oldState,
newState,
nextLegacyContext,
nextContext,
);

if (shouldUpdate) {
Expand All @@ -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();
}
Expand Down Expand Up @@ -1075,7 +1144,7 @@ function updateClassInstance(
// if shouldComponentUpdate returns false.
instance.props = newProps;
instance.state = newState;
instance.context = nextLegacyContext;
instance.context = nextContext;

return shouldUpdate;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@ describe 'ReactCoffeeScriptClass', ->
class Foo extends React.Component
constructor: ->
@contextTypes = {}
@contextType = {}
@propTypes = {}

getInitialState: ->
Expand All @@ -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
Expand Down

0 comments on commit 4b68a64

Please sign in to comment.