@samitouri / QOS-React / commits / 4b68a6498b

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);