Support class component static contextType attribute (#13728)
* Support class component static contextType attribute
Brian Vaughn committed
Sep 25, 2018 at 15:49 UTC
4b68a6498ba1bd1b81208a9340e0c405f45d4cc4
6 files changed
+382
-61
packages/react-reconciler/src/ReactFiberBeginWork.js
+19
-1
@@ -59,7 +59,6 @@ import warningWithoutStack from 'shared/warningWithoutStack';
59
import * as ReactCurrentFiber from './ReactCurrentFiber';
60
import {cancelWorkTimer} from './ReactDebugFiberPerf';
61
62
-import {applyDerivedStateFromProps} from './ReactFiberClassComponent';
62
import {
63
mountChildFibers,
64
reconcileChildFibers,
@@ -97,6 +96,7 @@ import {
96
} from './ReactFiberHydrationContext';
97
import {
98
adoptClassInstance,
99
+ applyDerivedStateFromProps,
100
constructClassInstance,
101
mountClassInstance,
102
resumeMountClassInstance,
@@ -109,11 +109,13 @@ import {resolveLazyComponentTag} from './ReactFiber';
109
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
110
111
let didWarnAboutBadClass;
112
+let didWarnAboutContextTypeOnFunctionalComponent;
113
let didWarnAboutGetDerivedStateOnFunctionalComponent;
114
let didWarnAboutStatelessRefs;
115
116
if (__DEV__) {
117
didWarnAboutBadClass = {};
118
+ didWarnAboutContextTypeOnFunctionalComponent = {};
119
didWarnAboutGetDerivedStateOnFunctionalComponent = {};
120
didWarnAboutStatelessRefs = {};
121
}
@@ -805,6 +807,22 @@ function mountIndeterminateComponent(
807
] = true;
808
}
809
}
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
+ }
826
}
827
reconcileChildren(current, workInProgress, value, renderExpirationTime);
828
memoizeProps(workInProgress, props);
packages/react-reconciler/src/ReactFiberClassComponent.js
+116
-47
@@ -65,6 +65,8 @@ let didWarnAboutUndefinedDerivedState;
65
let warnOnUndefinedDerivedState;
66
let warnOnInvalidCallback;
67
let didWarnAboutDirectlyAssigningPropsToState;
68
+let didWarnAboutContextTypeAndContextTypes;
69
+let didWarnAboutInvalidateContextType;
70
71
if (__DEV__) {
72
didWarnAboutStateAssignmentForComponent = new Set();
@@ -73,6 +75,8 @@ if (__DEV__) {
75
didWarnAboutLegacyLifecyclesAndDerivedState = new Set();
76
didWarnAboutDirectlyAssigningPropsToState = new Set();
77
didWarnAboutUndefinedDerivedState = new Set();
78
+ didWarnAboutContextTypeAndContextTypes = new Set();
79
+ didWarnAboutInvalidateContextType = new Set();
80
81
const didWarnOnInvalidCallback = new Set();
82
@@ -234,7 +238,7 @@ function checkShouldComponentUpdate(
238
newProps,
239
oldState,
240
newState,
237
- nextLegacyContext,
241
+ nextContext,
242
) {
243
const instance = workInProgress.stateNode;
244
if (typeof instance.shouldComponentUpdate === 'function') {
@@ -242,7 +246,7 @@ function checkShouldComponentUpdate(
246
const shouldUpdate = instance.shouldComponentUpdate(
247
newProps,
248
newState,
245
- nextLegacyContext,
249
+ nextContext,
250
);
251
stopPhaseTimer();
252
@@ -319,6 +323,13 @@ function checkClassInstance(workInProgress: Fiber, ctor: any, newProps: any) {
323
'property to define propTypes instead.',
324
name,
325
);
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
+ );
333
const noInstanceContextTypes = !instance.contextTypes;
334
warningWithoutStack(
335
noInstanceContextTypes,
@@ -326,6 +337,36 @@ function checkClassInstance(workInProgress: Fiber, ctor: any, newProps: any) {
337
'property to define contextTypes instead.',
338
name,
339
);
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
+
370
const noComponentShouldUpdate =
371
typeof instance.componentShouldUpdate !== 'function';
372
warningWithoutStack(
@@ -475,12 +516,25 @@ function constructClassInstance(
516
props: any,
517
renderExpirationTime: ExpirationTime,
518
): 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
+ }
538
539
// Instantiate twice to help detect side-effects.
540
if (__DEV__) {
@@ -587,7 +641,7 @@ function constructClassInstance(
641
642
// Cache unmasked context so we can avoid recreating masked context unless necessary.
643
// ReactFiberContext usually updates this cache but can't for newly-created instances.
590
- if (isContextConsumer) {
644
+ if (isLegacyContextConsumer) {
645
cacheContext(workInProgress, unmaskedContext, context);
646
}
647
@@ -625,15 +679,15 @@ function callComponentWillReceiveProps(
679
workInProgress,
680
instance,
681
newProps,
628
- nextLegacyContext,
682
+ nextContext,
683
) {
684
const oldState = instance.state;
685
startPhaseTimer(workInProgress, 'componentWillReceiveProps');
686
if (typeof instance.componentWillReceiveProps === 'function') {
633
- instance.componentWillReceiveProps(newProps, nextLegacyContext);
687
+ instance.componentWillReceiveProps(newProps, nextContext);
688
}
689
if (typeof instance.UNSAFE_componentWillReceiveProps === 'function') {
636
- instance.UNSAFE_componentWillReceiveProps(newProps, nextLegacyContext);
690
+ instance.UNSAFE_componentWillReceiveProps(newProps, nextContext);
691
}
692
stopPhaseTimer();
693
@@ -668,12 +722,21 @@ function mountClassInstance(
722
}
723
724
const instance = workInProgress.stateNode;
671
- const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
672
-
725
instance.props = newProps;
726
instance.state = workInProgress.memoizedState;
727
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
+ }
740
741
if (__DEV__) {
742
if (instance.state === newProps) {
@@ -774,15 +837,22 @@ function resumeMountClassInstance(
837
instance.props = oldProps;
838
839
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
+ }
856
857
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
858
const hasNewLifecycles =
@@ -800,12 +870,12 @@ function resumeMountClassInstance(
870
(typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
871
typeof instance.componentWillReceiveProps === 'function')
872
) {
803
- if (oldProps !== newProps || oldContext !== nextLegacyContext) {
873
+ if (oldProps !== newProps || oldContext !== nextContext) {
874
callComponentWillReceiveProps(
875
workInProgress,
876
instance,
877
newProps,
808
- nextLegacyContext,
878
+ nextContext,
879
);
880
}
881
}
@@ -858,7 +928,7 @@ function resumeMountClassInstance(
928
newProps,
929
oldState,
930
newState,
861
- nextLegacyContext,
931
+ nextContext,
932
);
933
934
if (shouldUpdate) {
@@ -898,7 +968,7 @@ function resumeMountClassInstance(
968
// if shouldComponentUpdate returns false.
969
instance.props = newProps;
970
instance.state = newState;
901
- instance.context = nextLegacyContext;
971
+ instance.context = nextContext;
972
973
return shouldUpdate;
974
}
@@ -917,15 +987,18 @@ function updateClassInstance(
987
instance.props = oldProps;
988
989
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
+ }
1002
1003
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
1004
const hasNewLifecycles =
@@ -943,12 +1016,12 @@ function updateClassInstance(
1016
(typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
1017
typeof instance.componentWillReceiveProps === 'function')
1018
) {
946
- if (oldProps !== newProps || oldContext !== nextLegacyContext) {
1019
+ if (oldProps !== newProps || oldContext !== nextContext) {
1020
callComponentWillReceiveProps(
1021
workInProgress,
1022
instance,
1023
newProps,
951
- nextLegacyContext,
1024
+ nextContext,
1025
);
1026
}
1027
}
@@ -1015,7 +1088,7 @@ function updateClassInstance(
1088
newProps,
1089
oldState,
1090
newState,
1018
- nextLegacyContext,
1091
+ nextContext,
1092
);
1093
1094
if (shouldUpdate) {
@@ -1028,14 +1101,10 @@ function updateClassInstance(
1101
) {
1102
startPhaseTimer(workInProgress, 'componentWillUpdate');
1103
if (typeof instance.componentWillUpdate === 'function') {
1031
- instance.componentWillUpdate(newProps, newState, nextLegacyContext);
1104
+ instance.componentWillUpdate(newProps, newState, nextContext);
1105
}
1106
if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
1034
- instance.UNSAFE_componentWillUpdate(
1035
- newProps,
1036
- newState,
1037
- nextLegacyContext,
1038
- );
1107
+ instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);
1108
}
1109
stopPhaseTimer();
1110
}
@@ -1075,7 +1144,7 @@ function updateClassInstance(
1144
// if shouldComponentUpdate returns false.
1145
instance.props = newProps;
1146
instance.state = newState;
1078
- instance.context = nextLegacyContext;
1147
+ instance.context = nextContext;
1148
1149
return shouldUpdate;
1150
}
packages/react/src/__tests__/ReactCoffeeScriptClass-test.coffee
+2
@@ -392,6 +392,7 @@ describe 'ReactCoffeeScriptClass', ->
392
class Foo extends React.Component
393
constructor: ->
394
@contextTypes = {}
395
+ @contextType = {}
396
@propTypes = {}
397
398
getInitialState: ->
@@ -413,6 +414,7 @@ describe 'ReactCoffeeScriptClass', ->
414
'getDefaultProps was defined on Foo, a plain JavaScript class.',
415
'propTypes was defined as an instance property on Foo.',
416
'contextTypes was defined as an instance property on Foo.',
417
+ 'contextType was defined as an instance property on Foo.',
418
], {withoutStack: true})
419
expect(getInitialStateWasCalled).toBe false
420
expect(getDefaultPropsWasCalled).toBe false
packages/react/src/__tests__/ReactContextValidator-test.js
+241
-13
@@ -67,9 +67,16 @@ describe('ReactContextValidator', () => {
67
});
68
69
it('should pass next context to lifecycles', () => {
70
- let actualComponentWillReceiveProps;
71
- let actualShouldComponentUpdate;
72
- let actualComponentWillUpdate;
70
+ let componentDidMountContext;
71
+ let componentDidUpdateContext;
72
+ let componentWillReceivePropsContext;
73
+ let componentWillReceivePropsNextContext;
74
+ let componentWillUpdateContext;
75
+ let componentWillUpdateNextContext;
76
+ let constructorContext;
77
+ let renderContext;
78
+ let shouldComponentUpdateContext;
79
+ let shouldComponentUpdateNextContext;
80
81
class Parent extends React.Component {
82
getChildContext() {
@@ -78,7 +85,6 @@ describe('ReactContextValidator', () => {
85
bar: 'bar',
86
};
87
}
81
-
88
render() {
89
return <Component />;
90
}
@@ -89,23 +95,34 @@ describe('ReactContextValidator', () => {
95
};
96
97
class Component extends React.Component {
98
+ constructor(props, context) {
99
+ super(props, context);
100
+ constructorContext = context;
101
+ }
102
UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
93
- actualComponentWillReceiveProps = nextContext;
103
+ componentWillReceivePropsContext = this.context;
104
+ componentWillReceivePropsNextContext = nextContext;
105
return true;
106
}
96
-
107
shouldComponentUpdate(nextProps, nextState, nextContext) {
98
- actualShouldComponentUpdate = nextContext;
108
+ shouldComponentUpdateContext = this.context;
109
+ shouldComponentUpdateNextContext = nextContext;
110
return true;
111
}
101
-
112
UNSAFE_componentWillUpdate(nextProps, nextState, nextContext) {
103
- actualComponentWillUpdate = nextContext;
113
+ componentWillUpdateContext = this.context;
114
+ componentWillUpdateNextContext = nextContext;
115
}
105
-
116
render() {
117
+ renderContext = this.context;
118
return <div />;
119
}
120
+ componentDidMount() {
121
+ componentDidMountContext = this.context;
122
+ }
123
+ componentDidUpdate() {
124
+ componentDidUpdateContext = this.context;
125
+ }
126
}
127
Component.contextTypes = {
128
foo: PropTypes.string,
@@ -113,10 +130,18 @@ describe('ReactContextValidator', () => {
130
131
const container = document.createElement('div');
132
ReactDOM.render(<Parent foo="abc" />, container);
133
+ expect(constructorContext).toEqual({foo: 'abc'});
134
+ expect(renderContext).toEqual({foo: 'abc'});
135
+ expect(componentDidMountContext).toEqual({foo: 'abc'});
136
ReactDOM.render(<Parent foo="def" />, container);
117
- expect(actualComponentWillReceiveProps).toEqual({foo: 'def'});
118
- expect(actualShouldComponentUpdate).toEqual({foo: 'def'});
119
- expect(actualComponentWillUpdate).toEqual({foo: 'def'});
137
+ expect(componentWillReceivePropsContext).toEqual({foo: 'abc'});
138
+ expect(componentWillReceivePropsNextContext).toEqual({foo: 'def'});
139
+ expect(shouldComponentUpdateContext).toEqual({foo: 'abc'});
140
+ expect(shouldComponentUpdateNextContext).toEqual({foo: 'def'});
141
+ expect(componentWillUpdateContext).toEqual({foo: 'abc'});
142
+ expect(componentWillUpdateNextContext).toEqual({foo: 'def'});
143
+ expect(renderContext).toEqual({foo: 'def'});
144
+ expect(componentDidUpdateContext).toEqual({foo: 'def'});
145
});
146
147
it('should check context types', () => {
@@ -338,4 +363,207 @@ describe('ReactContextValidator', () => {
363
expect(childContext.bar).toBeUndefined();
364
expect(childContext.foo).toBe('FOO');
365
});
366
+
367
+ it('should pass next context to lifecycles', () => {
368
+ let componentDidMountContext;
369
+ let componentDidUpdateContext;
370
+ let componentWillReceivePropsContext;
371
+ let componentWillReceivePropsNextContext;
372
+ let componentWillUpdateContext;
373
+ let componentWillUpdateNextContext;
374
+ let constructorContext;
375
+ let renderContext;
376
+ let shouldComponentUpdateWasCalled = false;
377
+
378
+ const Context = React.createContext();
379
+
380
+ class Component extends React.Component {
381
+ static contextType = Context;
382
+ constructor(props, context) {
383
+ super(props, context);
384
+ constructorContext = context;
385
+ }
386
+ UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
387
+ componentWillReceivePropsContext = this.context;
388
+ componentWillReceivePropsNextContext = nextContext;
389
+ return true;
390
+ }
391
+ shouldComponentUpdate(nextProps, nextState, nextContext) {
392
+ shouldComponentUpdateWasCalled = true;
393
+ return true;
394
+ }
395
+ UNSAFE_componentWillUpdate(nextProps, nextState, nextContext) {
396
+ componentWillUpdateContext = this.context;
397
+ componentWillUpdateNextContext = nextContext;
398
+ }
399
+ render() {
400
+ renderContext = this.context;
401
+ return <div />;
402
+ }
403
+ componentDidMount() {
404
+ componentDidMountContext = this.context;
405
+ }
406
+ componentDidUpdate() {
407
+ componentDidUpdateContext = this.context;
408
+ }
409
+ }
410
+
411
+ const firstContext = {foo: 123};
412
+ const secondContext = {bar: 456};
413
+
414
+ const container = document.createElement('div');
415
+ ReactDOM.render(
416
+ <Context.Provider value={firstContext}>
417
+ <Component />
418
+ </Context.Provider>,
419
+ container,
420
+ );
421
+ expect(constructorContext).toBe(firstContext);
422
+ expect(renderContext).toBe(firstContext);
423
+ expect(componentDidMountContext).toBe(firstContext);
424
+ ReactDOM.render(
425
+ <Context.Provider value={secondContext}>
426
+ <Component />
427
+ </Context.Provider>,
428
+ container,
429
+ );
430
+ expect(componentWillReceivePropsContext).toBe(firstContext);
431
+ expect(componentWillReceivePropsNextContext).toBe(secondContext);
432
+ expect(componentWillUpdateContext).toBe(firstContext);
433
+ expect(componentWillUpdateNextContext).toBe(secondContext);
434
+ expect(renderContext).toBe(secondContext);
435
+ expect(componentDidUpdateContext).toBe(secondContext);
436
+
437
+ // sCU is not called in this case because React force updates when a provider re-renders
438
+ expect(shouldComponentUpdateWasCalled).toBe(false);
439
+ });
440
+
441
+ it('should warn if both contextType and contextTypes are defined', () => {
442
+ const Context = React.createContext();
443
+
444
+ class ParentContextProvider extends React.Component {
445
+ static childContextTypes = {
446
+ foo: PropTypes.string,
447
+ };
448
+ getChildContext() {
449
+ return {
450
+ foo: 'FOO',
451
+ };
452
+ }
453
+ render() {
454
+ return this.props.children;
455
+ }
456
+ }
457
+
458
+ class ComponentA extends React.Component {
459
+ static contextTypes = {
460
+ foo: PropTypes.string.isRequired,
461
+ };
462
+ static contextType = Context;
463
+ render() {
464
+ return <div />;
465
+ }
466
+ }
467
+ class ComponentB extends React.Component {
468
+ static contextTypes = {
469
+ foo: PropTypes.string.isRequired,
470
+ };
471
+ static contextType = Context;
472
+ render() {
473
+ return <div />;
474
+ }
475
+ }
476
+
477
+ expect(() =>
478
+ ReactTestUtils.renderIntoDocument(
479
+ <ParentContextProvider>
480
+ <ComponentA />
481
+ </ParentContextProvider>,
482
+ ),
483
+ ).toWarnDev(
484
+ 'Warning: ComponentA declares both contextTypes and contextType static properties. ' +
485
+ 'The legacy contextTypes property will be ignored.',
486
+ {withoutStack: true},
487
+ );
488
+
489
+ // Warnings should be deduped by component type
490
+ ReactTestUtils.renderIntoDocument(
491
+ <ParentContextProvider>
492
+ <ComponentA />
493
+ </ParentContextProvider>,
494
+ );
495
+
496
+ expect(() =>
497
+ ReactTestUtils.renderIntoDocument(
498
+ <ParentContextProvider>
499
+ <ComponentB />
500
+ </ParentContextProvider>,
501
+ ),
502
+ ).toWarnDev(
503
+ 'Warning: ComponentB declares both contextTypes and contextType static properties. ' +
504
+ 'The legacy contextTypes property will be ignored.',
505
+ {withoutStack: true},
506
+ );
507
+ });
508
+
509
+ it('should warn if an invalid contextType is defined', () => {
510
+ const Context = React.createContext();
511
+
512
+ class ComponentA extends React.Component {
513
+ static contextType = Context.Provider;
514
+ render() {
515
+ return <div />;
516
+ }
517
+ }
518
+ class ComponentB extends React.Component {
519
+ static contextType = Context.Provider;
520
+ render() {
521
+ return <div />;
522
+ }
523
+ }
524
+
525
+ expect(() => ReactTestUtils.renderIntoDocument(<ComponentA />)).toWarnDev(
526
+ 'Warning: ComponentA defines an invalid contextType. ' +
527
+ 'contextType should point to the Context object returned by React.createContext(). ' +
528
+ 'Did you accidentally pass the Context.Provider instead?',
529
+ {withoutStack: true},
530
+ );
531
+
532
+ // Warnings should be deduped by component type
533
+ ReactTestUtils.renderIntoDocument(<ComponentA />);
534
+
535
+ expect(() => ReactTestUtils.renderIntoDocument(<ComponentB />)).toWarnDev(
536
+ 'Warning: ComponentB defines an invalid contextType. ' +
537
+ 'contextType should point to the Context object returned by React.createContext(). ' +
538
+ 'Did you accidentally pass the Context.Provider instead?',
539
+ {withoutStack: true},
540
+ );
541
+ });
542
+
543
+ it('should warn if you define contextType on a functional component', () => {
544
+ const Context = React.createContext();
545
+
546
+ function ComponentA() {
547
+ return <div />;
548
+ }
549
+ ComponentA.contextType = Context;
550
+
551
+ function ComponentB() {
552
+ return <div />;
553
+ }
554
+ ComponentB.contextType = Context;
555
+
556
+ expect(() => ReactTestUtils.renderIntoDocument(<ComponentA />)).toWarnDev(
557
+ 'Warning: ComponentA: Stateless functional components do not support contextType.',
558
+ {withoutStack: true},
559
+ );
560
+
561
+ // Warnings should be deduped by component type
562
+ ReactTestUtils.renderIntoDocument(<ComponentA />);
563
+
564
+ expect(() => ReactTestUtils.renderIntoDocument(<ComponentB />)).toWarnDev(
565
+ 'Warning: ComponentB: Stateless functional components do not support contextType.',
566
+ {withoutStack: true},
567
+ );
568
+ });
569
});
packages/react/src/__tests__/ReactES6Class-test.js
+2
@@ -435,6 +435,7 @@ describe('ReactES6Class', () => {
435
constructor() {
436
super();
437
this.contextTypes = {};
438
+ this.contextType = {};
439
this.propTypes = {};
440
}
441
getInitialState() {
@@ -455,6 +456,7 @@ describe('ReactES6Class', () => {
456
'getInitialState was defined on Foo, a plain JavaScript class.',
457
'getDefaultProps was defined on Foo, a plain JavaScript class.',
458
'propTypes was defined as an instance property on Foo.',
459
+ 'contextType was defined as an instance property on Foo.',
460
'contextTypes was defined as an instance property on Foo.',
461
],
462
{withoutStack: true},
packages/react/src/__tests__/ReactTypeScriptClass-test.ts
+2
@@ -239,6 +239,7 @@ let getInitialStateWasCalled = false;
239
let getDefaultPropsWasCalled = false;
240
class ClassicProperties extends React.Component {
241
contextTypes = {};
242
+ contextType = {};
243
propTypes = {};
244
getDefaultProps() {
245
getDefaultPropsWasCalled = true;
@@ -594,6 +595,7 @@ describe('ReactTypeScriptClass', function() {
595
'a plain JavaScript class.',
596
'propTypes was defined as an instance property on ClassicProperties.',
597
'contextTypes was defined as an instance property on ClassicProperties.',
598
+ 'contextType was defined as an instance property on ClassicProperties.',
599
], {withoutStack: true});
600
expect(getInitialStateWasCalled).toBe(false);
601
expect(getDefaultPropsWasCalled).toBe(false);