DevTools: Read context values from context dependencies (#28467)
Sebastian Silbermann committed
Mar 20, 2024 at 12:16 UTC
5cec48e145f6b43879b9ef40cdda812b88fff31c
1 file changed
+61
-5
packages/react-debug-tools/src/ReactDebugHooks.js
+61
-5
@@ -16,6 +16,8 @@ import type {
16
ReactDebugInfo,
17
} from 'shared/ReactTypes';
18
import type {
19
+ ContextDependency,
20
+ Dependencies,
21
Fiber,
22
Dispatcher as DispatcherType,
23
} from 'react-reconciler/src/ReactInternalTypes';
@@ -33,6 +35,7 @@ import {
35
REACT_MEMO_CACHE_SENTINEL,
36
REACT_CONTEXT_TYPE,
37
} from 'shared/ReactSymbols';
38
+import hasOwnProperty from 'shared/hasOwnProperty';
39
40
type CurrentDispatcherRef = typeof ReactSharedInternals.ReactCurrentDispatcher;
41
@@ -139,6 +142,7 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
142
143
let currentFiber: null | Fiber = null;
144
let currentHook: null | Hook = null;
145
+let currentContextDependency: null | ContextDependency<mixed> = null;
146
147
function nextHook(): null | Hook {
148
const hook = currentHook;
@@ -149,8 +153,29 @@ function nextHook(): null | Hook {
153
}
154
155
function readContext<T>(context: ReactContext<T>): T {
152
- // For now we don't expose readContext usage in the hooks debugging info.
153
- return context._currentValue;
156
+ if (currentFiber === null) {
157
+ // Hook inspection without access to the Fiber tree
158
+ // e.g. when warming up the primitive stack cache or during `ReactDebugTools.inspectHooks()`.
159
+ return context._currentValue;
160
+ } else {
161
+ if (currentContextDependency === null) {
162
+ throw new Error(
163
+ 'Context reads do not line up with context dependencies. This is a bug in React Debug Tools.',
164
+ );
165
+ }
166
+
167
+ // For now we don't expose readContext usage in the hooks debugging info.
168
+ const value = hasOwnProperty.call(currentContextDependency, 'memoizedValue')
169
+ ? // $FlowFixMe[incompatible-use] Flow thinks `hasOwnProperty` mutates `currentContextDependency`
170
+ ((currentContextDependency.memoizedValue: any): T)
171
+ : // Before React 18, we did not have `memoizedValue` so we rely on `setupContexts` in those versions.
172
+ // $FlowFixMe[incompatible-use] Flow thinks `hasOwnProperty` mutates `currentContextDependency`
173
+ ((currentContextDependency.context._currentValue: any): T);
174
+ // $FlowFixMe[incompatible-use] Flow thinks `hasOwnProperty` mutates `currentContextDependency`
175
+ currentContextDependency = currentContextDependency.next;
176
+
177
+ return value;
178
+ }
179
}
180
181
const SuspenseException: mixed = new Error(
@@ -218,14 +243,15 @@ function use<T>(usable: Usable<T>): T {
243
}
244
245
function useContext<T>(context: ReactContext<T>): T {
246
+ const value = readContext(context);
247
hookLog.push({
248
displayName: context.displayName || null,
249
primitive: 'Context',
250
stackError: new Error(),
225
- value: context._currentValue,
251
+ value: value,
252
debugInfo: null,
253
});
228
- return context._currentValue;
254
+ return value;
255
}
256
257
function useState<S>(
@@ -1090,15 +1116,44 @@ export function inspectHooksOfFiber(
1116
currentHook = (fiber.memoizedState: Hook);
1117
currentFiber = fiber;
1118
1119
+ if (hasOwnProperty.call(currentFiber, 'dependencies')) {
1120
+ // $FlowFixMe[incompatible-use]: Flow thinks hasOwnProperty might have nulled `currentFiber`
1121
+ const dependencies = currentFiber.dependencies;
1122
+ currentContextDependency =
1123
+ dependencies !== null ? dependencies.firstContext : null;
1124
+ } else if (hasOwnProperty.call(currentFiber, 'dependencies_old')) {
1125
+ const dependencies: Dependencies = (currentFiber: any).dependencies_old;
1126
+ currentContextDependency =
1127
+ dependencies !== null ? dependencies.firstContext : null;
1128
+ } else if (hasOwnProperty.call(currentFiber, 'dependencies_new')) {
1129
+ const dependencies: Dependencies = (currentFiber: any).dependencies_new;
1130
+ currentContextDependency =
1131
+ dependencies !== null ? dependencies.firstContext : null;
1132
+ } else if (hasOwnProperty.call(currentFiber, 'contextDependencies')) {
1133
+ const contextDependencies = (currentFiber: any).contextDependencies;
1134
+ currentContextDependency =
1135
+ contextDependencies !== null ? contextDependencies.first : null;
1136
+ } else {
1137
+ throw new Error(
1138
+ 'Unsupported React version. This is a bug in React Debug Tools.',
1139
+ );
1140
+ }
1141
+
1142
const type = fiber.type;
1143
let props = fiber.memoizedProps;
1144
if (type !== fiber.elementType) {
1145
props = resolveDefaultProps(type, props);
1146
}
1147
1148
+ // Only used for versions of React without memoized context value in context dependencies.
1149
const contextMap = new Map<ReactContext<any>, any>();
1150
try {
1101
- setupContexts(contextMap, fiber);
1151
+ if (
1152
+ currentContextDependency !== null &&
1153
+ !hasOwnProperty.call(currentContextDependency, 'memoizedValue')
1154
+ ) {
1155
+ setupContexts(contextMap, fiber);
1156
+ }
1157
1158
if (fiber.tag === ForwardRef) {
1159
return inspectHooksOfForwardRef(
@@ -1113,6 +1168,7 @@ export function inspectHooksOfFiber(
1168
} finally {
1169
currentFiber = null;
1170
currentHook = null;
1171
+ currentContextDependency = null;
1172
1173
restoreContexts(contextMap);
1174
}