@samitouri / QOS-React-2 / commits / 5cec48e145

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 }