Skip to content

Commit 5cec48e

Browse files
authored
DevTools: Read context values from context dependencies (#28467)
1 parent 29a6ca3 commit 5cec48e

File tree

1 file changed

+61
-5
lines changed

1 file changed

+61
-5
lines changed

packages/react-debug-tools/src/ReactDebugHooks.js

+61-5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import type {
1616
ReactDebugInfo,
1717
} from 'shared/ReactTypes';
1818
import type {
19+
ContextDependency,
20+
Dependencies,
1921
Fiber,
2022
Dispatcher as DispatcherType,
2123
} from 'react-reconciler/src/ReactInternalTypes';
@@ -33,6 +35,7 @@ import {
3335
REACT_MEMO_CACHE_SENTINEL,
3436
REACT_CONTEXT_TYPE,
3537
} from 'shared/ReactSymbols';
38+
import hasOwnProperty from 'shared/hasOwnProperty';
3639

3740
type CurrentDispatcherRef = typeof ReactSharedInternals.ReactCurrentDispatcher;
3841

@@ -139,6 +142,7 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
139142

140143
let currentFiber: null | Fiber = null;
141144
let currentHook: null | Hook = null;
145+
let currentContextDependency: null | ContextDependency<mixed> = null;
142146

143147
function nextHook(): null | Hook {
144148
const hook = currentHook;
@@ -149,8 +153,29 @@ function nextHook(): null | Hook {
149153
}
150154

151155
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+
}
154179
}
155180

156181
const SuspenseException: mixed = new Error(
@@ -218,14 +243,15 @@ function use<T>(usable: Usable<T>): T {
218243
}
219244

220245
function useContext<T>(context: ReactContext<T>): T {
246+
const value = readContext(context);
221247
hookLog.push({
222248
displayName: context.displayName || null,
223249
primitive: 'Context',
224250
stackError: new Error(),
225-
value: context._currentValue,
251+
value: value,
226252
debugInfo: null,
227253
});
228-
return context._currentValue;
254+
return value;
229255
}
230256

231257
function useState<S>(
@@ -1090,15 +1116,44 @@ export function inspectHooksOfFiber(
10901116
currentHook = (fiber.memoizedState: Hook);
10911117
currentFiber = fiber;
10921118

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+
10931142
const type = fiber.type;
10941143
let props = fiber.memoizedProps;
10951144
if (type !== fiber.elementType) {
10961145
props = resolveDefaultProps(type, props);
10971146
}
10981147

1148+
// Only used for versions of React without memoized context value in context dependencies.
10991149
const contextMap = new Map<ReactContext<any>, any>();
11001150
try {
1101-
setupContexts(contextMap, fiber);
1151+
if (
1152+
currentContextDependency !== null &&
1153+
!hasOwnProperty.call(currentContextDependency, 'memoizedValue')
1154+
) {
1155+
setupContexts(contextMap, fiber);
1156+
}
11021157

11031158
if (fiber.tag === ForwardRef) {
11041159
return inspectHooksOfForwardRef(
@@ -1113,6 +1168,7 @@ export function inspectHooksOfFiber(
11131168
} finally {
11141169
currentFiber = null;
11151170
currentHook = null;
1171+
currentContextDependency = null;
11161172

11171173
restoreContexts(contextMap);
11181174
}

0 commit comments

Comments
 (0)