Skip to content

Commit 1d9a0cb

Browse files
bvaughnjetoneza
authored andcommitted
Support class component static contextType attribute (facebook#13728)
* Support class component static contextType attribute
1 parent 4fe20de commit 1d9a0cb

File tree

6 files changed

+382
-61
lines changed

6 files changed

+382
-61
lines changed

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ import warningWithoutStack from 'shared/warningWithoutStack';
5959
import * as ReactCurrentFiber from './ReactCurrentFiber';
6060
import {cancelWorkTimer} from './ReactDebugFiberPerf';
6161

62-
import {applyDerivedStateFromProps} from './ReactFiberClassComponent';
6362
import {
6463
mountChildFibers,
6564
reconcileChildFibers,
@@ -97,6 +96,7 @@ import {
9796
} from './ReactFiberHydrationContext';
9897
import {
9998
adoptClassInstance,
99+
applyDerivedStateFromProps,
100100
constructClassInstance,
101101
mountClassInstance,
102102
resumeMountClassInstance,
@@ -109,11 +109,13 @@ import {resolveLazyComponentTag} from './ReactFiber';
109109
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
110110

111111
let didWarnAboutBadClass;
112+
let didWarnAboutContextTypeOnFunctionalComponent;
112113
let didWarnAboutGetDerivedStateOnFunctionalComponent;
113114
let didWarnAboutStatelessRefs;
114115

115116
if (__DEV__) {
116117
didWarnAboutBadClass = {};
118+
didWarnAboutContextTypeOnFunctionalComponent = {};
117119
didWarnAboutGetDerivedStateOnFunctionalComponent = {};
118120
didWarnAboutStatelessRefs = {};
119121
}
@@ -805,6 +807,22 @@ function mountIndeterminateComponent(
805807
] = true;
806808
}
807809
}
810+
811+
if (
812+
typeof Component.contextType === 'object' &&
813+
Component.contextType !== null
814+
) {
815+
const componentName = getComponentName(Component) || 'Unknown';
816+
817+
if (!didWarnAboutContextTypeOnFunctionalComponent[componentName]) {
818+
warningWithoutStack(
819+
false,
820+
'%s: Stateless functional components do not support contextType.',
821+
componentName,
822+
);
823+
didWarnAboutContextTypeOnFunctionalComponent[componentName] = true;
824+
}
825+
}
808826
}
809827
reconcileChildren(current, workInProgress, value, renderExpirationTime);
810828
memoizeProps(workInProgress, props);

packages/react-reconciler/src/ReactFiberClassComponent.js

Lines changed: 116 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ let didWarnAboutUndefinedDerivedState;
6565
let warnOnUndefinedDerivedState;
6666
let warnOnInvalidCallback;
6767
let didWarnAboutDirectlyAssigningPropsToState;
68+
let didWarnAboutContextTypeAndContextTypes;
69+
let didWarnAboutInvalidateContextType;
6870

6971
if (__DEV__) {
7072
didWarnAboutStateAssignmentForComponent = new Set();
@@ -73,6 +75,8 @@ if (__DEV__) {
7375
didWarnAboutLegacyLifecyclesAndDerivedState = new Set();
7476
didWarnAboutDirectlyAssigningPropsToState = new Set();
7577
didWarnAboutUndefinedDerivedState = new Set();
78+
didWarnAboutContextTypeAndContextTypes = new Set();
79+
didWarnAboutInvalidateContextType = new Set();
7680

7781
const didWarnOnInvalidCallback = new Set();
7882

@@ -234,15 +238,15 @@ function checkShouldComponentUpdate(
234238
newProps,
235239
oldState,
236240
newState,
237-
nextLegacyContext,
241+
nextContext,
238242
) {
239243
const instance = workInProgress.stateNode;
240244
if (typeof instance.shouldComponentUpdate === 'function') {
241245
startPhaseTimer(workInProgress, 'shouldComponentUpdate');
242246
const shouldUpdate = instance.shouldComponentUpdate(
243247
newProps,
244248
newState,
245-
nextLegacyContext,
249+
nextContext,
246250
);
247251
stopPhaseTimer();
248252

@@ -319,13 +323,50 @@ function checkClassInstance(workInProgress: Fiber, ctor: any, newProps: any) {
319323
'property to define propTypes instead.',
320324
name,
321325
);
326+
const noInstanceContextType = !instance.contextType;
327+
warningWithoutStack(
328+
noInstanceContextType,
329+
'contextType was defined as an instance property on %s. Use a static ' +
330+
'property to define contextType instead.',
331+
name,
332+
);
322333
const noInstanceContextTypes = !instance.contextTypes;
323334
warningWithoutStack(
324335
noInstanceContextTypes,
325336
'contextTypes was defined as an instance property on %s. Use a static ' +
326337
'property to define contextTypes instead.',
327338
name,
328339
);
340+
341+
if (
342+
ctor.contextType &&
343+
ctor.contextTypes &&
344+
!didWarnAboutContextTypeAndContextTypes.has(ctor)
345+
) {
346+
didWarnAboutContextTypeAndContextTypes.add(ctor);
347+
warningWithoutStack(
348+
false,
349+
'%s declares both contextTypes and contextType static properties. ' +
350+
'The legacy contextTypes property will be ignored.',
351+
name,
352+
);
353+
}
354+
355+
if (
356+
ctor.contextType &&
357+
typeof ctor.contextType.unstable_read !== 'function' &&
358+
!didWarnAboutInvalidateContextType.has(ctor)
359+
) {
360+
didWarnAboutInvalidateContextType.add(ctor);
361+
warningWithoutStack(
362+
false,
363+
'%s defines an invalid contextType. ' +
364+
'contextType should point to the Context object returned by React.createContext(). ' +
365+
'Did you accidentally pass the Context.Provider instead?',
366+
name,
367+
);
368+
}
369+
329370
const noComponentShouldUpdate =
330371
typeof instance.componentShouldUpdate !== 'function';
331372
warningWithoutStack(
@@ -475,12 +516,25 @@ function constructClassInstance(
475516
props: any,
476517
renderExpirationTime: ExpirationTime,
477518
): any {
478-
const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
479-
const contextTypes = ctor.contextTypes;
480-
const isContextConsumer = contextTypes !== null && contextTypes !== undefined;
481-
const context = isContextConsumer
482-
? getMaskedContext(workInProgress, unmaskedContext)
483-
: emptyContextObject;
519+
let isLegacyContextConsumer = false;
520+
let unmaskedContext = emptyContextObject;
521+
let context = null;
522+
const contextType = ctor.contextType;
523+
if (
524+
typeof contextType === 'object' &&
525+
contextType !== null &&
526+
typeof contextType.unstable_read === 'function'
527+
) {
528+
context = (contextType: any).unstable_read();
529+
} else {
530+
unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
531+
const contextTypes = ctor.contextTypes;
532+
isLegacyContextConsumer =
533+
contextTypes !== null && contextTypes !== undefined;
534+
context = isLegacyContextConsumer
535+
? getMaskedContext(workInProgress, unmaskedContext)
536+
: emptyContextObject;
537+
}
484538

485539
// Instantiate twice to help detect side-effects.
486540
if (__DEV__) {
@@ -587,7 +641,7 @@ function constructClassInstance(
587641

588642
// Cache unmasked context so we can avoid recreating masked context unless necessary.
589643
// ReactFiberContext usually updates this cache but can't for newly-created instances.
590-
if (isContextConsumer) {
644+
if (isLegacyContextConsumer) {
591645
cacheContext(workInProgress, unmaskedContext, context);
592646
}
593647

@@ -625,15 +679,15 @@ function callComponentWillReceiveProps(
625679
workInProgress,
626680
instance,
627681
newProps,
628-
nextLegacyContext,
682+
nextContext,
629683
) {
630684
const oldState = instance.state;
631685
startPhaseTimer(workInProgress, 'componentWillReceiveProps');
632686
if (typeof instance.componentWillReceiveProps === 'function') {
633-
instance.componentWillReceiveProps(newProps, nextLegacyContext);
687+
instance.componentWillReceiveProps(newProps, nextContext);
634688
}
635689
if (typeof instance.UNSAFE_componentWillReceiveProps === 'function') {
636-
instance.UNSAFE_componentWillReceiveProps(newProps, nextLegacyContext);
690+
instance.UNSAFE_componentWillReceiveProps(newProps, nextContext);
637691
}
638692
stopPhaseTimer();
639693

@@ -668,12 +722,21 @@ function mountClassInstance(
668722
}
669723

670724
const instance = workInProgress.stateNode;
671-
const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
672-
673725
instance.props = newProps;
674726
instance.state = workInProgress.memoizedState;
675727
instance.refs = emptyRefsObject;
676-
instance.context = getMaskedContext(workInProgress, unmaskedContext);
728+
729+
const contextType = ctor.contextType;
730+
if (
731+
typeof contextType === 'object' &&
732+
contextType !== null &&
733+
typeof contextType.unstable_read === 'function'
734+
) {
735+
instance.context = (contextType: any).unstable_read();
736+
} else {
737+
const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
738+
instance.context = getMaskedContext(workInProgress, unmaskedContext);
739+
}
677740

678741
if (__DEV__) {
679742
if (instance.state === newProps) {
@@ -774,15 +837,22 @@ function resumeMountClassInstance(
774837
instance.props = oldProps;
775838

776839
const oldContext = instance.context;
777-
const nextLegacyUnmaskedContext = getUnmaskedContext(
778-
workInProgress,
779-
ctor,
780-
true,
781-
);
782-
const nextLegacyContext = getMaskedContext(
783-
workInProgress,
784-
nextLegacyUnmaskedContext,
785-
);
840+
const contextType = ctor.contextType;
841+
let nextContext;
842+
if (
843+
typeof contextType === 'object' &&
844+
contextType !== null &&
845+
typeof contextType.unstable_read === 'function'
846+
) {
847+
nextContext = (contextType: any).unstable_read();
848+
} else {
849+
const nextLegacyUnmaskedContext = getUnmaskedContext(
850+
workInProgress,
851+
ctor,
852+
true,
853+
);
854+
nextContext = getMaskedContext(workInProgress, nextLegacyUnmaskedContext);
855+
}
786856

787857
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
788858
const hasNewLifecycles =
@@ -800,12 +870,12 @@ function resumeMountClassInstance(
800870
(typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
801871
typeof instance.componentWillReceiveProps === 'function')
802872
) {
803-
if (oldProps !== newProps || oldContext !== nextLegacyContext) {
873+
if (oldProps !== newProps || oldContext !== nextContext) {
804874
callComponentWillReceiveProps(
805875
workInProgress,
806876
instance,
807877
newProps,
808-
nextLegacyContext,
878+
nextContext,
809879
);
810880
}
811881
}
@@ -858,7 +928,7 @@ function resumeMountClassInstance(
858928
newProps,
859929
oldState,
860930
newState,
861-
nextLegacyContext,
931+
nextContext,
862932
);
863933

864934
if (shouldUpdate) {
@@ -898,7 +968,7 @@ function resumeMountClassInstance(
898968
// if shouldComponentUpdate returns false.
899969
instance.props = newProps;
900970
instance.state = newState;
901-
instance.context = nextLegacyContext;
971+
instance.context = nextContext;
902972

903973
return shouldUpdate;
904974
}
@@ -917,15 +987,18 @@ function updateClassInstance(
917987
instance.props = oldProps;
918988

919989
const oldContext = instance.context;
920-
const nextLegacyUnmaskedContext = getUnmaskedContext(
921-
workInProgress,
922-
ctor,
923-
true,
924-
);
925-
const nextLegacyContext = getMaskedContext(
926-
workInProgress,
927-
nextLegacyUnmaskedContext,
928-
);
990+
const contextType = ctor.contextType;
991+
let nextContext;
992+
if (
993+
typeof contextType === 'object' &&
994+
contextType !== null &&
995+
typeof contextType.unstable_read === 'function'
996+
) {
997+
nextContext = (contextType: any).unstable_read();
998+
} else {
999+
const nextUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
1000+
nextContext = getMaskedContext(workInProgress, nextUnmaskedContext);
1001+
}
9291002

9301003
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
9311004
const hasNewLifecycles =
@@ -943,12 +1016,12 @@ function updateClassInstance(
9431016
(typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
9441017
typeof instance.componentWillReceiveProps === 'function')
9451018
) {
946-
if (oldProps !== newProps || oldContext !== nextLegacyContext) {
1019+
if (oldProps !== newProps || oldContext !== nextContext) {
9471020
callComponentWillReceiveProps(
9481021
workInProgress,
9491022
instance,
9501023
newProps,
951-
nextLegacyContext,
1024+
nextContext,
9521025
);
9531026
}
9541027
}
@@ -1015,7 +1088,7 @@ function updateClassInstance(
10151088
newProps,
10161089
oldState,
10171090
newState,
1018-
nextLegacyContext,
1091+
nextContext,
10191092
);
10201093

10211094
if (shouldUpdate) {
@@ -1028,14 +1101,10 @@ function updateClassInstance(
10281101
) {
10291102
startPhaseTimer(workInProgress, 'componentWillUpdate');
10301103
if (typeof instance.componentWillUpdate === 'function') {
1031-
instance.componentWillUpdate(newProps, newState, nextLegacyContext);
1104+
instance.componentWillUpdate(newProps, newState, nextContext);
10321105
}
10331106
if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
1034-
instance.UNSAFE_componentWillUpdate(
1035-
newProps,
1036-
newState,
1037-
nextLegacyContext,
1038-
);
1107+
instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);
10391108
}
10401109
stopPhaseTimer();
10411110
}
@@ -1075,7 +1144,7 @@ function updateClassInstance(
10751144
// if shouldComponentUpdate returns false.
10761145
instance.props = newProps;
10771146
instance.state = newState;
1078-
instance.context = nextLegacyContext;
1147+
instance.context = nextContext;
10791148

10801149
return shouldUpdate;
10811150
}

packages/react/src/__tests__/ReactCoffeeScriptClass-test.coffee

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@ describe 'ReactCoffeeScriptClass', ->
392392
class Foo extends React.Component
393393
constructor: ->
394394
@contextTypes = {}
395+
@contextType = {}
395396
@propTypes = {}
396397

397398
getInitialState: ->
@@ -413,6 +414,7 @@ describe 'ReactCoffeeScriptClass', ->
413414
'getDefaultProps was defined on Foo, a plain JavaScript class.',
414415
'propTypes was defined as an instance property on Foo.',
415416
'contextTypes was defined as an instance property on Foo.',
417+
'contextType was defined as an instance property on Foo.',
416418
], {withoutStack: true})
417419
expect(getInitialStateWasCalled).toBe false
418420
expect(getDefaultPropsWasCalled).toBe false

0 commit comments

Comments
 (0)