DevTools: Add support for use(Context) (#28233)
Sebastian Silbermann committed
Feb 8, 2024 at 18:47 UTC
04b59928d867dae1639f12f19700347d8f5d4cac
3 files changed
+72
-7
packages/react-debug-tools/src/ReactDebugHooks.js
+32
-7
@@ -12,6 +12,7 @@ import type {
12
ReactContext,
13
ReactProviderType,
14
StartTransitionOptions,
15
+ Usable,
16
} from 'shared/ReactTypes';
17
import type {
18
Fiber,
@@ -27,7 +28,10 @@ import {
28
ContextProvider,
29
ForwardRef,
30
} from 'react-reconciler/src/ReactWorkTags';
30
-import {REACT_MEMO_CACHE_SENTINEL} from 'shared/ReactSymbols';
31
+import {
32
+ REACT_MEMO_CACHE_SENTINEL,
33
+ REACT_CONTEXT_TYPE,
34
+} from 'shared/ReactSymbols';
35
36
type CurrentDispatcherRef = typeof ReactSharedInternals.ReactCurrentDispatcher;
37
@@ -118,11 +122,30 @@ function readContext<T>(context: ReactContext<T>): T {
122
return context._currentValue;
123
}
124
121
-function use<T>(): T {
122
- // TODO: What should this do if it receives an unresolved promise?
123
- throw new Error(
124
- 'Support for `use` not yet implemented in react-debug-tools.',
125
- );
125
+function use<T>(usable: Usable<T>): T {
126
+ if (usable !== null && typeof usable === 'object') {
127
+ // $FlowFixMe[method-unbinding]
128
+ if (typeof usable.then === 'function') {
129
+ // TODO: What should this do if it receives an unresolved promise?
130
+ throw new Error(
131
+ 'Support for `use(Promise)` not yet implemented in react-debug-tools.',
132
+ );
133
+ } else if (usable.$$typeof === REACT_CONTEXT_TYPE) {
134
+ const context: ReactContext<T> = (usable: any);
135
+ const value = readContext(context);
136
+
137
+ hookLog.push({
138
+ primitive: 'Use',
139
+ stackError: new Error(),
140
+ value,
141
+ });
142
+
143
+ return value;
144
+ }
145
+ }
146
+
147
+ // eslint-disable-next-line react-internal/safe-string-coercion
148
+ throw new Error('An unsupported type was passed to use(): ' + String(usable));
149
}
150
151
function useContext<T>(context: ReactContext<T>): T {
@@ -660,7 +683,9 @@ function buildTree(
683
// For now, the "id" of stateful hooks is just the stateful hook index.
684
// Custom hooks have no ids, nor do non-stateful native hooks (e.g. Context, DebugValue).
685
const id =
663
- primitive === 'Context' || primitive === 'DebugValue'
686
+ primitive === 'Context' ||
687
+ primitive === 'DebugValue' ||
688
+ primitive === 'Use'
689
? null
690
: nativeHookID++;
691
packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js
+38
@@ -1108,6 +1108,44 @@ describe('ReactHooksInspectionIntegration', () => {
1108
]);
1109
});
1110
1111
+ it('should support use(Context) hook', () => {
1112
+ const Context = React.createContext('default');
1113
+ function Foo() {
1114
+ const value = React.use(Context);
1115
+ React.useMemo(() => 'memo', []);
1116
+ React.useMemo(() => 'not used', []);
1117
+
1118
+ return value;
1119
+ }
1120
+
1121
+ const renderer = ReactTestRenderer.create(<Foo />);
1122
+ const childFiber = renderer.root.findByType(Foo)._currentFiber();
1123
+ const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
1124
+ expect(tree).toEqual([
1125
+ {
1126
+ id: null,
1127
+ isStateEditable: false,
1128
+ name: 'Use',
1129
+ value: 'default',
1130
+ subHooks: [],
1131
+ },
1132
+ {
1133
+ id: 0,
1134
+ isStateEditable: false,
1135
+ name: 'Memo',
1136
+ value: 'memo',
1137
+ subHooks: [],
1138
+ },
1139
+ {
1140
+ id: 1,
1141
+ isStateEditable: false,
1142
+ name: 'Memo',
1143
+ value: 'not used',
1144
+ subHooks: [],
1145
+ },
1146
+ ]);
1147
+ });
1148
+
1149
// @gate enableAsyncActions
1150
it('should support useOptimistic hook', () => {
1151
const useOptimistic = React.useOptimistic;
packages/react-devtools-shell/src/app/InspectableElements/CustomHooks.js
+2
@@ -19,6 +19,7 @@ import {
19
useEffect,
20
useOptimistic,
21
useState,
22
+ use,
23
} from 'react';
24
import {useFormState} from 'react-dom';
25
@@ -76,6 +77,7 @@ function FunctionWithHooks(props: any, ref: React$Ref<any>) {
77
// eslint-disable-next-line no-unused-vars
78
const contextValueA = useContext(ContextA);
79
useOptimistic<number, mixed>(1);
80
+ use(ContextA);
81
82
// eslint-disable-next-line no-unused-vars
83
const [_, __] = useState(object);