Skip to content

Commit 4ef6387

Browse files
hristo-kanchevbvaughn
authored andcommitted
[DevTools] [Context] Legacy Context (#16617)
* Added hasLegacyContext check. * Passed hasLegacyContext as prop to SelectedElement * Changing context labels based on hasLegacyContext * Fixed flow types. * Fixed typos. * Added tests for hasLegacyContext. * Renamed test. * Removed test imports.
1 parent c317fc2 commit 4ef6387

File tree

7 files changed

+146
-4
lines changed

7 files changed

+146
-4
lines changed

packages/react-devtools-shared/src/__tests__/inspectedElementContext-test.js

+115
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type Store from 'react-devtools-shared/src/devtools/store';
1515
describe('InspectedElementContext', () => {
1616
let React;
1717
let ReactDOM;
18+
let PropTypes;
1819
let TestRenderer: ReactTestRenderer;
1920
let bridge: FrontendBridge;
2021
let store: Store;
@@ -40,6 +41,7 @@ describe('InspectedElementContext', () => {
4041

4142
React = require('react');
4243
ReactDOM = require('react-dom');
44+
PropTypes = require('prop-types');
4345
TestUtils = require('react-dom/test-utils');
4446
TestRenderer = utils.requireTestRenderer();
4547

@@ -114,6 +116,119 @@ describe('InspectedElementContext', () => {
114116
done();
115117
});
116118

119+
it('should have hasLegacyContext flag set to either "true" or "false" depending on which context API is used.', async done => {
120+
const contextData = {
121+
bool: true,
122+
};
123+
124+
// Legacy Context API.
125+
class LegacyContextProvider extends React.Component<any> {
126+
static childContextTypes = {
127+
bool: PropTypes.bool,
128+
};
129+
getChildContext() {
130+
return contextData;
131+
}
132+
render() {
133+
return this.props.children;
134+
}
135+
}
136+
class LegacyContextConsumer extends React.Component<any> {
137+
static contextTypes = {
138+
bool: PropTypes.bool,
139+
};
140+
render() {
141+
return null;
142+
}
143+
}
144+
145+
// Modern Context API
146+
const BoolContext = React.createContext(contextData.bool);
147+
BoolContext.displayName = 'BoolContext';
148+
149+
class ModernContextType extends React.Component<any> {
150+
static contextType = BoolContext;
151+
render() {
152+
return null;
153+
}
154+
}
155+
156+
const ModernContext = React.createContext();
157+
ModernContext.displayName = 'ModernContext';
158+
159+
const container = document.createElement('div');
160+
await utils.actAsync(() =>
161+
ReactDOM.render(
162+
<React.Fragment>
163+
<LegacyContextProvider>
164+
<LegacyContextConsumer />
165+
</LegacyContextProvider>
166+
<BoolContext.Consumer>{value => null}</BoolContext.Consumer>
167+
<ModernContextType />
168+
<ModernContext.Provider value={contextData}>
169+
<ModernContext.Consumer>{value => null}</ModernContext.Consumer>
170+
</ModernContext.Provider>
171+
</React.Fragment>,
172+
container,
173+
),
174+
);
175+
176+
const ids = [
177+
{
178+
// <LegacyContextConsumer />
179+
id: ((store.getElementIDAtIndex(1): any): number),
180+
shouldHaveLegacyContext: true,
181+
},
182+
{
183+
// <BoolContext.Consumer>
184+
id: ((store.getElementIDAtIndex(2): any): number),
185+
shouldHaveLegacyContext: false,
186+
},
187+
{
188+
// <ModernContextType />
189+
id: ((store.getElementIDAtIndex(3): any): number),
190+
shouldHaveLegacyContext: false,
191+
},
192+
{
193+
// <ModernContext.Consumer>
194+
id: ((store.getElementIDAtIndex(5): any): number),
195+
shouldHaveLegacyContext: false,
196+
},
197+
];
198+
199+
function Suspender({target, shouldHaveLegacyContext}) {
200+
const {getInspectedElement} = React.useContext(InspectedElementContext);
201+
const inspectedElement = getInspectedElement(target);
202+
203+
expect(inspectedElement.context).not.toBe(null);
204+
expect(inspectedElement.hasLegacyContext).toBe(shouldHaveLegacyContext);
205+
206+
return null;
207+
}
208+
209+
for (let i = 0; i < ids.length; i++) {
210+
const {id, shouldHaveLegacyContext} = ids[i];
211+
212+
await utils.actAsync(
213+
() =>
214+
TestRenderer.create(
215+
<Contexts
216+
defaultSelectedElementID={id}
217+
defaultSelectedElementIndex={0}>
218+
<React.Suspense fallback={null}>
219+
<Suspender
220+
target={id}
221+
shouldHaveLegacyContext={shouldHaveLegacyContext}
222+
/>
223+
</React.Suspense>
224+
</Contexts>,
225+
),
226+
false,
227+
);
228+
}
229+
done();
230+
});
231+
117232
it('should poll for updates for the currently selected element', async done => {
118233
const Example = () => null;
119234

packages/react-devtools-shared/src/backend/legacy/renderer.js

+3
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,9 @@ export function attach(
753753
// Can view component source location.
754754
canViewSource: type === ElementTypeClass || type === ElementTypeFunction,
755755

756+
// Only legacy context exists in legacy versions.
757+
hasLegacyContext: true,
758+
756759
displayName: displayName,
757760

758761
type: type,

packages/react-devtools-shared/src/backend/renderer.js

+17-2
Original file line numberDiff line numberDiff line change
@@ -2107,6 +2107,8 @@ export function attach(
21072107
type,
21082108
} = fiber;
21092109

2110+
const elementType = getElementTypeForFiber(fiber);
2111+
21102112
const usesHooks =
21112113
(tag === FunctionComponent ||
21122114
tag === SimpleMemoComponent ||
@@ -2128,7 +2130,14 @@ export function attach(
21282130
) {
21292131
canViewSource = true;
21302132
if (stateNode && stateNode.context != null) {
2131-
context = stateNode.context;
2133+
// Don't show an empty context object for class components that don't use the context API.
2134+
const shouldHideContext =
2135+
elementType === ElementTypeClass &&
2136+
!(type.contextTypes || type.contextType);
2137+
2138+
if (!shouldHideContext) {
2139+
context = stateNode.context;
2140+
}
21322141
}
21332142
} else if (
21342143
typeSymbol === CONTEXT_CONSUMER_NUMBER ||
@@ -2166,7 +2175,10 @@ export function attach(
21662175
}
21672176
}
21682177

2178+
let hasLegacyContext = false;
21692179
if (context !== null) {
2180+
hasLegacyContext = !!type.contextTypes;
2181+
21702182
// To simplify hydration and display logic for context, wrap in a value object.
21712183
// Otherwise simple values (e.g. strings, booleans) become harder to handle.
21722184
context = {value: context};
@@ -2238,8 +2250,11 @@ export function attach(
22382250
// Can view component source location.
22392251
canViewSource,
22402252

2253+
// Does the component have legacy context attached to it.
2254+
hasLegacyContext,
2255+
22412256
displayName: getDisplayNameForFiber(fiber),
2242-
type: getElementTypeForFiber(fiber),
2257+
type: elementType,
22432258

22442259
// Inspectable properties.
22452260
// TODO Review sanitization approach for the below inspectable values.

packages/react-devtools-shared/src/backend/types.js

+3
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ export type InspectedElement = {|
163163
// Can view component source location.
164164
canViewSource: boolean,
165165

166+
// Does the component have legacy context attached to it.
167+
hasLegacyContext: boolean,
168+
166169
// Inspectable properties.
167170
context: Object | null,
168171
hooks: Object | null,

packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js

+2
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ function InspectedElementContextController({children}: Props) {
159159
canEditHooks,
160160
canToggleSuspense,
161161
canViewSource,
162+
hasLegacyContext,
162163
source,
163164
type,
164165
owners,
@@ -173,6 +174,7 @@ function InspectedElementContextController({children}: Props) {
173174
canEditHooks,
174175
canToggleSuspense,
175176
canViewSource,
177+
hasLegacyContext,
176178
id,
177179
source,
178180
type,

packages/react-devtools-shared/src/devtools/views/Components/SelectedElement.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ function InspectedElementView({
267267
canEditFunctionProps,
268268
canEditHooks,
269269
canToggleSuspense,
270+
hasLegacyContext,
270271
context,
271272
hooks,
272273
owners,
@@ -376,7 +377,7 @@ function InspectedElementView({
376377
)}
377378
<HooksTree canEditHooks={canEditHooks} hooks={hooks} id={id} />
378379
<InspectedElementTree
379-
label="context"
380+
label={hasLegacyContext ? 'legacy context' : 'context'}
380381
data={context}
381382
inspectPath={inspectContextPath}
382383
overrideValueFn={overrideContextFn}

packages/react-devtools-shared/src/devtools/views/Components/types.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ export type InspectedElement = {|
7171
// Can view component source location.
7272
canViewSource: boolean,
7373

74+
// Does the component have legacy context attached to it.
75+
hasLegacyContext: boolean,
76+
7477
// Inspectable properties.
7578
context: Object | null,
7679
hooks: Object | null,
@@ -80,7 +83,7 @@ export type InspectedElement = {|
8083
// List of owners
8184
owners: Array<Owner> | null,
8285

83-
// Location of component in source coude.
86+
// Location of component in source code.
8487
source: Source | null,
8588

8689
type: ElementType,

0 commit comments

Comments
 (0)