diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index e7f3fb0ee58ae..145dae4ddb0f4 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -177,15 +177,20 @@ function readContext(context: ReactContext): T { ); } + let value: T; // 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; + if (hasOwnProperty.call(currentContextDependency, 'memoizedValue')) { + // $FlowFixMe[incompatible-use] Flow thinks `hasOwnProperty` mutates `currentContextDependency` + value = ((currentContextDependency.memoizedValue: any): T); + + // $FlowFixMe[incompatible-use] Flow thinks `hasOwnProperty` mutates `currentContextDependency` + currentContextDependency = currentContextDependency.next; + } else { + // Before React 18, we did not have `memoizedValue` so we rely on `setupContexts` in those versions. + // Multiple reads of the same context were also only tracked as a single dependency. + // We just give up on advancing context dependencies and solely rely on `setupContexts`. + value = context._currentValue; + } return value; } diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js index b0d18c3b5d5ac..0c54464b72a4a 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js @@ -833,6 +833,45 @@ describe('ReactHooksInspectionIntegration', () => { `); }); + // @reactVersion >= 16.8 + it('should inspect the value of the current provider in useContext reading the same context multiple times', async () => { + const ContextA = React.createContext('default A'); + const ContextB = React.createContext('default B'); + function Foo(props) { + React.useContext(ContextA); + React.useContext(ContextA); + React.useContext(ContextB); + React.useContext(ContextB); + React.useContext(ContextA); + React.useContext(ContextB); + React.useContext(ContextB); + React.useContext(ContextB); + return null; + } + let renderer; + await act(() => { + renderer = ReactTestRenderer.create( + + + , + {unstable_isConcurrent: true}, + ); + }); + const childFiber = renderer.root.findByType(Foo)._currentFiber(); + const tree = ReactDebugTools.inspectHooksOfFiber(childFiber); + + expect(normalizeSourceLoc(tree)).toEqual([ + expect.objectContaining({value: 'contextual A'}), + expect.objectContaining({value: 'contextual A'}), + expect.objectContaining({value: 'default B'}), + expect.objectContaining({value: 'default B'}), + expect.objectContaining({value: 'contextual A'}), + expect.objectContaining({value: 'default B'}), + expect.objectContaining({value: 'default B'}), + expect.objectContaining({value: 'default B'}), + ]); + }); + it('should inspect forwardRef', async () => { const obj = function () {}; const Foo = React.forwardRef(function (props, ref) {