feat: honor displayName of context types (#18224)
* Revert "Revert "feat: honor displayName of context types (#18035)" (#18223)" This reverts commit 3ee812e6b67d52a6f262144e78721f9340c36a04. * Add warning of displayName is set on the consumer * dedupe warning
Brian Vaughn committed
Mar 5, 2020 at 10:13 UTC
d35f8a58184d1495ff54f4ad80c702e9468c89ee
3 files changed
+63
-2
packages/react/src/ReactContext.js
+15
@@ -57,6 +57,7 @@ export function createContext<T>(
57
58
let hasWarnedAboutUsingNestedContextConsumers = false;
59
let hasWarnedAboutUsingConsumerProvider = false;
60
+ let hasWarnedAboutDisplayNameOnConsumer = false;
61
62
if (__DEV__) {
63
// A separate object, but proxies back to the original context object for
@@ -120,6 +121,20 @@ export function createContext<T>(
121
return context.Consumer;
122
},
123
},
124
+ displayName: {
125
+ get() {
126
+ return context.displayName;
127
+ },
128
+ set() {
129
+ if (!hasWarnedAboutDisplayNameOnConsumer) {
130
+ console.warn(
131
+ 'Setting `displayName` on Context.Consumer has no effect. ' +
132
+ "You should set it directly on the context with Context.displayName = 'NamedContext'.",
133
+ );
134
+ hasWarnedAboutDisplayNameOnConsumer = true;
135
+ }
136
+ },
137
+ },
138
});
139
// $FlowFixMe: Flow complains about missing properties because it doesn't understand defineProperty
140
context.Consumer = Consumer;
packages/react/src/__tests__/ReactContextValidator-test.js
+39
@@ -18,6 +18,7 @@
18
let PropTypes;
19
let React;
20
let ReactDOM;
21
+let ReactDOMServer;
22
let ReactTestUtils;
23
24
describe('ReactContextValidator', () => {
@@ -27,6 +28,7 @@ describe('ReactContextValidator', () => {
28
PropTypes = require('prop-types');
29
React = require('react');
30
ReactDOM = require('react-dom');
31
+ ReactDOMServer = require('react-dom/server');
32
ReactTestUtils = require('react-dom/test-utils');
33
});
34
@@ -671,4 +673,41 @@ describe('ReactContextValidator', () => {
673
'Warning: ComponentB: Function components do not support contextType.',
674
);
675
});
676
+
677
+ it('should honor a displayName if set on the context type', () => {
678
+ const Context = React.createContext(null);
679
+ Context.displayName = 'MyContextType';
680
+ function Validator() {
681
+ return null;
682
+ }
683
+ Validator.propTypes = {dontPassToSeeErrorStack: PropTypes.bool.isRequired};
684
+
685
+ expect(() => {
686
+ ReactDOMServer.renderToStaticMarkup(
687
+ <Context.Provider>
688
+ <Context.Consumer>{() => <Validator />}</Context.Consumer>
689
+ </Context.Provider>,
690
+ );
691
+ }).toErrorDev(
692
+ 'Warning: Failed prop type: The prop `dontPassToSeeErrorStack` is marked as required in `Validator`, but its value is `undefined`.\n' +
693
+ ' in Validator (at **)\n' +
694
+ ' in MyContextType.Consumer (at **)\n' +
695
+ ' in MyContextType.Provider (at **)',
696
+ );
697
+ });
698
+
699
+ it('warns if displayName is set on the consumer type', () => {
700
+ const Context = React.createContext(null);
701
+
702
+ expect(() => {
703
+ Context.Consumer.displayName = 'ignored';
704
+ }).toWarnDev(
705
+ 'Warning: Setting `displayName` on Context.Consumer has no effect. ' +
706
+ "You should set it directly on the context with Context.displayName = 'NamedContext'.",
707
+ {withoutStack: true},
708
+ );
709
+
710
+ // warning is deduped so subsequent setting is fine
711
+ Context.Consumer.displayName = 'ignored';
712
+ });
713
});
packages/shared/getComponentName.js
+9
-2
@@ -24,6 +24,7 @@ import {
24
REACT_BLOCK_TYPE,
25
} from 'shared/ReactSymbols';
26
import {refineResolvedLazyComponent} from 'shared/ReactLazyComponent';
27
+import type {ReactContext, ReactProviderType} from 'shared/ReactTypes';
28
29
function getWrappedName(
30
outerType: mixed,
@@ -37,6 +38,10 @@ function getWrappedName(
38
);
39
}
40
41
+function getContextName(type: ReactContext<any>) {
42
+ return type.displayName || 'Context';
43
+}
44
+
45
function getComponentName(type: mixed): string | null {
46
if (type == null) {
47
// Host root, text node or just invalid type.
@@ -73,9 +78,11 @@ function getComponentName(type: mixed): string | null {
78
if (typeof type === 'object') {
79
switch (type.$$typeof) {
80
case REACT_CONTEXT_TYPE:
76
- return 'Context.Consumer';
81
+ const context: ReactContext<any> = (type: any);
82
+ return getContextName(context) + '.Consumer';
83
case REACT_PROVIDER_TYPE:
78
- return 'Context.Provider';
84
+ const provider: ReactProviderType<any> = (type: any);
85
+ return getContextName(provider._context) + '.Provider';
86
case REACT_FORWARD_REF_TYPE:
87
return getWrappedName(type, type.render, 'ForwardRef');
88
case REACT_MEMO_TYPE: