Skip to content

Commit

Permalink
DevTools: Read context values from context dependencies (facebook#28467)
Browse files Browse the repository at this point in the history
  • Loading branch information
eps1lon authored and AndyPengc12 committed Apr 15, 2024
1 parent 03d8368 commit c50c3c0
Showing 1 changed file with 61 additions and 5 deletions.
66 changes: 61 additions & 5 deletions packages/react-debug-tools/src/ReactDebugHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import type {
ReactDebugInfo,
} from 'shared/ReactTypes';
import type {
ContextDependency,
Dependencies,
Fiber,
Dispatcher as DispatcherType,
} from 'react-reconciler/src/ReactInternalTypes';
Expand All @@ -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;

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

let currentFiber: null | Fiber = null;
let currentHook: null | Hook = null;
let currentContextDependency: null | ContextDependency<mixed> = null;

function nextHook(): null | Hook {
const hook = currentHook;
Expand All @@ -149,8 +153,29 @@ function nextHook(): null | Hook {
}

function readContext<T>(context: ReactContext<T>): 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(
Expand Down Expand Up @@ -218,14 +243,15 @@ function use<T>(usable: Usable<T>): T {
}

function useContext<T>(context: ReactContext<T>): 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<S>(
Expand Down Expand Up @@ -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<ReactContext<any>, any>();
try {
setupContexts(contextMap, fiber);
if (
currentContextDependency !== null &&
!hasOwnProperty.call(currentContextDependency, 'memoizedValue')
) {
setupContexts(contextMap, fiber);
}

if (fiber.tag === ForwardRef) {
return inspectHooksOfForwardRef(
Expand All @@ -1113,6 +1168,7 @@ export function inspectHooksOfFiber(
} finally {
currentFiber = null;
currentHook = null;
currentContextDependency = null;

restoreContexts(contextMap);
}
Expand Down

0 comments on commit c50c3c0

Please sign in to comment.