diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 8e1809435d724..a6770ccd34bfa 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -16,6 +16,8 @@ import type { ReactDebugInfo, } from 'shared/ReactTypes'; import type { + ContextDependency, + Dependencies, Fiber, Dispatcher as DispatcherType, } from 'react-reconciler/src/ReactInternalTypes'; @@ -33,6 +35,7 @@ import { REACT_MEMO_CACHE_SENTINEL, REACT_CONTEXT_TYPE, } from 'shared/ReactSymbols'; +import hasOwnProperty from 'shared/hasOwnProperty'; type CurrentDispatcherRef = typeof ReactSharedInternals.ReactCurrentDispatcher; @@ -139,6 +142,7 @@ function getPrimitiveStackCache(): Map> { let currentFiber: null | Fiber = null; let currentHook: null | Hook = null; +let currentContextDependency: null | ContextDependency = null; function nextHook(): null | Hook { const hook = currentHook; @@ -149,8 +153,29 @@ function nextHook(): null | Hook { } function readContext(context: ReactContext): T { - // For now we don't expose readContext usage in the hooks debugging info. - return context._currentValue; + if (currentFiber === null) { + // Hook inspection without access to the Fiber tree + // e.g. when warming up the primitive stack cache or during `ReactDebugTools.inspectHooks()`. + return context._currentValue; + } else { + if (currentContextDependency === null) { + throw new Error( + 'Context reads do not line up with context dependencies. This is a bug in React Debug Tools.', + ); + } + + // For now we don't expose readContext usage in the hooks debugging info. + const value = hasOwnProperty.call(currentContextDependency, 'memoizedValue') + ? // $FlowFixMe[incompatible-use] Flow thinks `hasOwnProperty` mutates `currentContextDependency` + ((currentContextDependency.memoizedValue: any): T) + : // Before React 18, we did not have `memoizedValue` so we rely on `setupContexts` in those versions. + // $FlowFixMe[incompatible-use] Flow thinks `hasOwnProperty` mutates `currentContextDependency` + ((currentContextDependency.context._currentValue: any): T); + // $FlowFixMe[incompatible-use] Flow thinks `hasOwnProperty` mutates `currentContextDependency` + currentContextDependency = currentContextDependency.next; + + return value; + } } const SuspenseException: mixed = new Error( @@ -218,14 +243,15 @@ function use(usable: Usable): T { } function useContext(context: ReactContext): T { + const value = readContext(context); hookLog.push({ displayName: context.displayName || null, primitive: 'Context', stackError: new Error(), - value: context._currentValue, + value: value, debugInfo: null, }); - return context._currentValue; + return value; } function useState( @@ -1090,15 +1116,44 @@ export function inspectHooksOfFiber( currentHook = (fiber.memoizedState: Hook); currentFiber = fiber; + if (hasOwnProperty.call(currentFiber, 'dependencies')) { + // $FlowFixMe[incompatible-use]: Flow thinks hasOwnProperty might have nulled `currentFiber` + const dependencies = currentFiber.dependencies; + currentContextDependency = + dependencies !== null ? dependencies.firstContext : null; + } else if (hasOwnProperty.call(currentFiber, 'dependencies_old')) { + const dependencies: Dependencies = (currentFiber: any).dependencies_old; + currentContextDependency = + dependencies !== null ? dependencies.firstContext : null; + } else if (hasOwnProperty.call(currentFiber, 'dependencies_new')) { + const dependencies: Dependencies = (currentFiber: any).dependencies_new; + currentContextDependency = + dependencies !== null ? dependencies.firstContext : null; + } else if (hasOwnProperty.call(currentFiber, 'contextDependencies')) { + const contextDependencies = (currentFiber: any).contextDependencies; + currentContextDependency = + contextDependencies !== null ? contextDependencies.first : null; + } else { + throw new Error( + 'Unsupported React version. This is a bug in React Debug Tools.', + ); + } + const type = fiber.type; let props = fiber.memoizedProps; if (type !== fiber.elementType) { props = resolveDefaultProps(type, props); } + // Only used for versions of React without memoized context value in context dependencies. const contextMap = new Map, any>(); try { - setupContexts(contextMap, fiber); + if ( + currentContextDependency !== null && + !hasOwnProperty.call(currentContextDependency, 'memoizedValue') + ) { + setupContexts(contextMap, fiber); + } if (fiber.tag === ForwardRef) { return inspectHooksOfForwardRef( @@ -1113,6 +1168,7 @@ export function inspectHooksOfFiber( } finally { currentFiber = null; currentHook = null; + currentContextDependency = null; restoreContexts(contextMap); }