diff --git a/packages/react-devtools-shared/src/__tests__/inspectedElementContext-test.js b/packages/react-devtools-shared/src/__tests__/inspectedElementContext-test.js index 73a3e916c283..ca5d074eefea 100644 --- a/packages/react-devtools-shared/src/__tests__/inspectedElementContext-test.js +++ b/packages/react-devtools-shared/src/__tests__/inspectedElementContext-test.js @@ -15,6 +15,7 @@ import type Store from 'react-devtools-shared/src/devtools/store'; describe('InspectedElementContext', () => { let React; let ReactDOM; + let PropTypes; let TestRenderer: ReactTestRenderer; let bridge: FrontendBridge; let store: Store; @@ -40,6 +41,7 @@ describe('InspectedElementContext', () => { React = require('react'); ReactDOM = require('react-dom'); + PropTypes = require('prop-types'); TestUtils = require('react-dom/test-utils'); TestRenderer = utils.requireTestRenderer(); @@ -114,6 +116,119 @@ describe('InspectedElementContext', () => { done(); }); + it('should have hasLegacyContext flag set to either "true" or "false" depending on which context API is used.', async done => { + const contextData = { + bool: true, + }; + + // Legacy Context API. + class LegacyContextProvider extends React.Component { + static childContextTypes = { + bool: PropTypes.bool, + }; + getChildContext() { + return contextData; + } + render() { + return this.props.children; + } + } + class LegacyContextConsumer extends React.Component { + static contextTypes = { + bool: PropTypes.bool, + }; + render() { + return null; + } + } + + // Modern Context API + const BoolContext = React.createContext(contextData.bool); + BoolContext.displayName = 'BoolContext'; + + class ModernContextType extends React.Component { + static contextType = BoolContext; + render() { + return null; + } + } + + const ModernContext = React.createContext(); + ModernContext.displayName = 'ModernContext'; + + const container = document.createElement('div'); + await utils.actAsync(() => + ReactDOM.render( + + + + + {value => null} + + + {value => null} + + , + container, + ), + ); + + const ids = [ + { + // + id: ((store.getElementIDAtIndex(1): any): number), + shouldHaveLegacyContext: true, + }, + { + // + id: ((store.getElementIDAtIndex(2): any): number), + shouldHaveLegacyContext: false, + }, + { + // + id: ((store.getElementIDAtIndex(3): any): number), + shouldHaveLegacyContext: false, + }, + { + // + id: ((store.getElementIDAtIndex(5): any): number), + shouldHaveLegacyContext: false, + }, + ]; + + function Suspender({target, shouldHaveLegacyContext}) { + const {getInspectedElement} = React.useContext(InspectedElementContext); + const inspectedElement = getInspectedElement(target); + + expect(inspectedElement.context).not.toBe(null); + expect(inspectedElement.hasLegacyContext).toBe(shouldHaveLegacyContext); + + return null; + } + + for (let i = 0; i < ids.length; i++) { + const {id, shouldHaveLegacyContext} = ids[i]; + + await utils.actAsync( + () => + TestRenderer.create( + + + + + , + ), + false, + ); + } + done(); + }); + it('should poll for updates for the currently selected element', async done => { const Example = () => null; diff --git a/packages/react-devtools-shared/src/backend/legacy/renderer.js b/packages/react-devtools-shared/src/backend/legacy/renderer.js index 94db3b25ada8..bc2fb30ccf8d 100644 --- a/packages/react-devtools-shared/src/backend/legacy/renderer.js +++ b/packages/react-devtools-shared/src/backend/legacy/renderer.js @@ -753,6 +753,9 @@ export function attach( // Can view component source location. canViewSource: type === ElementTypeClass || type === ElementTypeFunction, + // Only legacy context exists in legacy versions. + hasLegacyContext: true, + displayName: displayName, type: type, diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js index e8a6d8ee8d5a..520f583013d9 100644 --- a/packages/react-devtools-shared/src/backend/renderer.js +++ b/packages/react-devtools-shared/src/backend/renderer.js @@ -2107,6 +2107,8 @@ export function attach( type, } = fiber; + const elementType = getElementTypeForFiber(fiber); + const usesHooks = (tag === FunctionComponent || tag === SimpleMemoComponent || @@ -2128,7 +2130,14 @@ export function attach( ) { canViewSource = true; if (stateNode && stateNode.context != null) { - context = stateNode.context; + // Don't show an empty context object for class components that don't use the context API. + const shouldHideContext = + elementType === ElementTypeClass && + !(type.contextTypes || type.contextType); + + if (!shouldHideContext) { + context = stateNode.context; + } } } else if ( typeSymbol === CONTEXT_CONSUMER_NUMBER || @@ -2166,7 +2175,10 @@ export function attach( } } + let hasLegacyContext = false; if (context !== null) { + hasLegacyContext = !!type.contextTypes; + // To simplify hydration and display logic for context, wrap in a value object. // Otherwise simple values (e.g. strings, booleans) become harder to handle. context = {value: context}; @@ -2238,8 +2250,11 @@ export function attach( // Can view component source location. canViewSource, + // Does the component have legacy context attached to it. + hasLegacyContext, + displayName: getDisplayNameForFiber(fiber), - type: getElementTypeForFiber(fiber), + type: elementType, // Inspectable properties. // TODO Review sanitization approach for the below inspectable values. diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js index 87f9fc14a5f6..245817b5c660 100644 --- a/packages/react-devtools-shared/src/backend/types.js +++ b/packages/react-devtools-shared/src/backend/types.js @@ -163,6 +163,9 @@ export type InspectedElement = {| // Can view component source location. canViewSource: boolean, + // Does the component have legacy context attached to it. + hasLegacyContext: boolean, + // Inspectable properties. context: Object | null, hooks: Object | null, diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js index f58671eb4e3f..e2e23e75abd3 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js @@ -159,6 +159,7 @@ function InspectedElementContextController({children}: Props) { canEditHooks, canToggleSuspense, canViewSource, + hasLegacyContext, source, type, owners, @@ -173,6 +174,7 @@ function InspectedElementContextController({children}: Props) { canEditHooks, canToggleSuspense, canViewSource, + hasLegacyContext, id, source, type, diff --git a/packages/react-devtools-shared/src/devtools/views/Components/SelectedElement.js b/packages/react-devtools-shared/src/devtools/views/Components/SelectedElement.js index a553f494af1a..8ac93a1995c0 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/SelectedElement.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/SelectedElement.js @@ -267,6 +267,7 @@ function InspectedElementView({ canEditFunctionProps, canEditHooks, canToggleSuspense, + hasLegacyContext, context, hooks, owners, @@ -376,7 +377,7 @@ function InspectedElementView({ )} | null, - // Location of component in source coude. + // Location of component in source code. source: Source | null, type: ElementType,