[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.
Hristo Kanchev committed
Sep 10, 2019 at 22:30 UTC
4ef6387d6e79e87c3ebc11a53847fd26ad5e71d9
7 files changed
+146
-4
packages/react-devtools-shared/src/__tests__/inspectedElementContext-test.js
+115
@@ -15,6 +15,7 @@ import type Store from 'react-devtools-shared/src/devtools/store';
15
describe('InspectedElementContext', () => {
16
let React;
17
let ReactDOM;
18
+ let PropTypes;
19
let TestRenderer: ReactTestRenderer;
20
let bridge: FrontendBridge;
21
let store: Store;
@@ -40,6 +41,7 @@ describe('InspectedElementContext', () => {
41
42
React = require('react');
43
ReactDOM = require('react-dom');
44
+ PropTypes = require('prop-types');
45
TestUtils = require('react-dom/test-utils');
46
TestRenderer = utils.requireTestRenderer();
47
@@ -114,6 +116,119 @@ describe('InspectedElementContext', () => {
116
done();
117
});
118
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
+
232
it('should poll for updates for the currently selected element', async done => {
233
const Example = () => null;
234
packages/react-devtools-shared/src/backend/legacy/renderer.js
+3
@@ -753,6 +753,9 @@ export function attach(
753
// Can view component source location.
754
canViewSource: type === ElementTypeClass || type === ElementTypeFunction,
755
756
+ // Only legacy context exists in legacy versions.
757
+ hasLegacyContext: true,
758
+
759
displayName: displayName,
760
761
type: type,
packages/react-devtools-shared/src/backend/renderer.js
+17
-2
@@ -2107,6 +2107,8 @@ export function attach(
2107
type,
2108
} = fiber;
2109
2110
+ const elementType = getElementTypeForFiber(fiber);
2111
+
2112
const usesHooks =
2113
(tag === FunctionComponent ||
2114
tag === SimpleMemoComponent ||
@@ -2128,7 +2130,14 @@ export function attach(
2130
) {
2131
canViewSource = true;
2132
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
+ }
2141
}
2142
} else if (
2143
typeSymbol === CONTEXT_CONSUMER_NUMBER ||
@@ -2166,7 +2175,10 @@ export function attach(
2175
}
2176
}
2177
2178
+ let hasLegacyContext = false;
2179
if (context !== null) {
2180
+ hasLegacyContext = !!type.contextTypes;
2181
+
2182
// To simplify hydration and display logic for context, wrap in a value object.
2183
// Otherwise simple values (e.g. strings, booleans) become harder to handle.
2184
context = {value: context};
@@ -2238,8 +2250,11 @@ export function attach(
2250
// Can view component source location.
2251
canViewSource,
2252
2253
+ // Does the component have legacy context attached to it.
2254
+ hasLegacyContext,
2255
+
2256
displayName: getDisplayNameForFiber(fiber),
2242
- type: getElementTypeForFiber(fiber),
2257
+ type: elementType,
2258
2259
// Inspectable properties.
2260
// TODO Review sanitization approach for the below inspectable values.
packages/react-devtools-shared/src/backend/types.js
+3
@@ -163,6 +163,9 @@ export type InspectedElement = {|
163
// Can view component source location.
164
canViewSource: boolean,
165
166
+ // Does the component have legacy context attached to it.
167
+ hasLegacyContext: boolean,
168
+
169
// Inspectable properties.
170
context: Object | null,
171
hooks: Object | null,
packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js
+2
@@ -159,6 +159,7 @@ function InspectedElementContextController({children}: Props) {
159
canEditHooks,
160
canToggleSuspense,
161
canViewSource,
162
+ hasLegacyContext,
163
source,
164
type,
165
owners,
@@ -173,6 +174,7 @@ function InspectedElementContextController({children}: Props) {
174
canEditHooks,
175
canToggleSuspense,
176
canViewSource,
177
+ hasLegacyContext,
178
id,
179
source,
180
type,
packages/react-devtools-shared/src/devtools/views/Components/SelectedElement.js
+2
-1
@@ -267,6 +267,7 @@ function InspectedElementView({
267
canEditFunctionProps,
268
canEditHooks,
269
canToggleSuspense,
270
+ hasLegacyContext,
271
context,
272
hooks,
273
owners,
@@ -376,7 +377,7 @@ function InspectedElementView({
377
)}
378
<HooksTree canEditHooks={canEditHooks} hooks={hooks} id={id} />
379
<InspectedElementTree
379
- label="context"
380
+ label={hasLegacyContext ? 'legacy context' : 'context'}
381
data={context}
382
inspectPath={inspectContextPath}
383
overrideValueFn={overrideContextFn}
packages/react-devtools-shared/src/devtools/views/Components/types.js
+4
-1
@@ -71,6 +71,9 @@ export type InspectedElement = {|
71
// Can view component source location.
72
canViewSource: boolean,
73
74
+ // Does the component have legacy context attached to it.
75
+ hasLegacyContext: boolean,
76
+
77
// Inspectable properties.
78
context: Object | null,
79
hooks: Object | null,
@@ -80,7 +83,7 @@ export type InspectedElement = {|
83
// List of owners
84
owners: Array<Owner> | null,
85
83
- // Location of component in source coude.
86
+ // Location of component in source code.
87
source: Source | null,
88
89
type: ElementType,