From d35f8a58184d1495ff54f4ad80c702e9468c89ee Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 5 Mar 2020 10:13:52 -0800 Subject: [PATCH] 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 --- packages/react/src/ReactContext.js | 15 +++++++ .../__tests__/ReactContextValidator-test.js | 39 +++++++++++++++++++ packages/shared/getComponentName.js | 11 +++++- 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/packages/react/src/ReactContext.js b/packages/react/src/ReactContext.js index cbcd3373c816..f24ea6cd0a3a 100644 --- a/packages/react/src/ReactContext.js +++ b/packages/react/src/ReactContext.js @@ -57,6 +57,7 @@ export function createContext( let hasWarnedAboutUsingNestedContextConsumers = false; let hasWarnedAboutUsingConsumerProvider = false; + let hasWarnedAboutDisplayNameOnConsumer = false; if (__DEV__) { // A separate object, but proxies back to the original context object for @@ -120,6 +121,20 @@ export function createContext( return context.Consumer; }, }, + displayName: { + get() { + return context.displayName; + }, + set() { + if (!hasWarnedAboutDisplayNameOnConsumer) { + console.warn( + 'Setting `displayName` on Context.Consumer has no effect. ' + + "You should set it directly on the context with Context.displayName = 'NamedContext'.", + ); + hasWarnedAboutDisplayNameOnConsumer = true; + } + }, + }, }); // $FlowFixMe: Flow complains about missing properties because it doesn't understand defineProperty context.Consumer = Consumer; diff --git a/packages/react/src/__tests__/ReactContextValidator-test.js b/packages/react/src/__tests__/ReactContextValidator-test.js index 0123d7091755..d4fdb4d7303e 100644 --- a/packages/react/src/__tests__/ReactContextValidator-test.js +++ b/packages/react/src/__tests__/ReactContextValidator-test.js @@ -18,6 +18,7 @@ let PropTypes; let React; let ReactDOM; +let ReactDOMServer; let ReactTestUtils; describe('ReactContextValidator', () => { @@ -27,6 +28,7 @@ describe('ReactContextValidator', () => { PropTypes = require('prop-types'); React = require('react'); ReactDOM = require('react-dom'); + ReactDOMServer = require('react-dom/server'); ReactTestUtils = require('react-dom/test-utils'); }); @@ -671,4 +673,41 @@ describe('ReactContextValidator', () => { 'Warning: ComponentB: Function components do not support contextType.', ); }); + + it('should honor a displayName if set on the context type', () => { + const Context = React.createContext(null); + Context.displayName = 'MyContextType'; + function Validator() { + return null; + } + Validator.propTypes = {dontPassToSeeErrorStack: PropTypes.bool.isRequired}; + + expect(() => { + ReactDOMServer.renderToStaticMarkup( + + {() => } + , + ); + }).toErrorDev( + 'Warning: Failed prop type: The prop `dontPassToSeeErrorStack` is marked as required in `Validator`, but its value is `undefined`.\n' + + ' in Validator (at **)\n' + + ' in MyContextType.Consumer (at **)\n' + + ' in MyContextType.Provider (at **)', + ); + }); + + it('warns if displayName is set on the consumer type', () => { + const Context = React.createContext(null); + + expect(() => { + Context.Consumer.displayName = 'ignored'; + }).toWarnDev( + 'Warning: Setting `displayName` on Context.Consumer has no effect. ' + + "You should set it directly on the context with Context.displayName = 'NamedContext'.", + {withoutStack: true}, + ); + + // warning is deduped so subsequent setting is fine + Context.Consumer.displayName = 'ignored'; + }); }); diff --git a/packages/shared/getComponentName.js b/packages/shared/getComponentName.js index e458338982dc..9817ea21963e 100644 --- a/packages/shared/getComponentName.js +++ b/packages/shared/getComponentName.js @@ -24,6 +24,7 @@ import { REACT_BLOCK_TYPE, } from 'shared/ReactSymbols'; import {refineResolvedLazyComponent} from 'shared/ReactLazyComponent'; +import type {ReactContext, ReactProviderType} from 'shared/ReactTypes'; function getWrappedName( outerType: mixed, @@ -37,6 +38,10 @@ function getWrappedName( ); } +function getContextName(type: ReactContext) { + return type.displayName || 'Context'; +} + function getComponentName(type: mixed): string | null { if (type == null) { // Host root, text node or just invalid type. @@ -73,9 +78,11 @@ function getComponentName(type: mixed): string | null { if (typeof type === 'object') { switch (type.$$typeof) { case REACT_CONTEXT_TYPE: - return 'Context.Consumer'; + const context: ReactContext = (type: any); + return getContextName(context) + '.Consumer'; case REACT_PROVIDER_TYPE: - return 'Context.Provider'; + const provider: ReactProviderType = (type: any); + return getContextName(provider._context) + '.Provider'; case REACT_FORWARD_REF_TYPE: return getWrappedName(type, type.render, 'ForwardRef'); case REACT_MEMO_TYPE: