Skip to content

Commit fea799d

Browse files
committed
DevTools: Fix inspecting components with multiple reads of the same Context in React 17
1 parent 50a07c1 commit fea799d

File tree

2 files changed

+51
-8
lines changed

2 files changed

+51
-8
lines changed

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

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -177,15 +177,20 @@ function readContext<T>(context: ReactContext<T>): T {
177177
);
178178
}
179179

180+
let value: T;
180181
// For now we don't expose readContext usage in the hooks debugging info.
181-
const value = hasOwnProperty.call(currentContextDependency, 'memoizedValue')
182-
? // $FlowFixMe[incompatible-use] Flow thinks `hasOwnProperty` mutates `currentContextDependency`
183-
((currentContextDependency.memoizedValue: any): T)
184-
: // Before React 18, we did not have `memoizedValue` so we rely on `setupContexts` in those versions.
185-
// $FlowFixMe[incompatible-use] Flow thinks `hasOwnProperty` mutates `currentContextDependency`
186-
((currentContextDependency.context._currentValue: any): T);
187-
// $FlowFixMe[incompatible-use] Flow thinks `hasOwnProperty` mutates `currentContextDependency`
188-
currentContextDependency = currentContextDependency.next;
182+
if (hasOwnProperty.call(currentContextDependency, 'memoizedValue')) {
183+
// $FlowFixMe[incompatible-use] Flow thinks `hasOwnProperty` mutates `currentContextDependency`
184+
value = ((currentContextDependency.memoizedValue: any): T);
185+
186+
// $FlowFixMe[incompatible-use] Flow thinks `hasOwnProperty` mutates `currentContextDependency`
187+
currentContextDependency = currentContextDependency.next;
188+
} else {
189+
// Before React 18, we did not have `memoizedValue` so we rely on `setupContexts` in those versions.
190+
// Multiple reads of the same context were also only tracked as a single dependency.
191+
// We just give up on advancing context dependencies and solely rely on `setupContexts`.
192+
value = context._currentValue;
193+
}
189194

190195
return value;
191196
}

packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,44 @@ describe('ReactHooksInspectionIntegration', () => {
833833
`);
834834
});
835835

836+
it('should inspect the value of the current provider in useContext reading the same context multiple times', async () => {
837+
const ContextA = React.createContext('default A');
838+
const ContextB = React.createContext('default B');
839+
function Foo(props) {
840+
React.useContext(ContextA);
841+
React.useContext(ContextA);
842+
React.useContext(ContextB);
843+
React.useContext(ContextB);
844+
React.useContext(ContextA);
845+
React.useContext(ContextB);
846+
React.useContext(ContextB);
847+
React.useContext(ContextB);
848+
return null;
849+
}
850+
let renderer;
851+
await act(() => {
852+
renderer = ReactTestRenderer.create(
853+
<ContextA.Provider value="contextual A">
854+
<Foo prop="prop" />
855+
</ContextA.Provider>,
856+
{unstable_isConcurrent: true},
857+
);
858+
});
859+
const childFiber = renderer.root.findByType(Foo)._currentFiber();
860+
const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
861+
862+
expect(normalizeSourceLoc(tree)).toEqual([
863+
expect.objectContaining({value: 'contextual A'}),
864+
expect.objectContaining({value: 'contextual A'}),
865+
expect.objectContaining({value: 'default B'}),
866+
expect.objectContaining({value: 'default B'}),
867+
expect.objectContaining({value: 'contextual A'}),
868+
expect.objectContaining({value: 'default B'}),
869+
expect.objectContaining({value: 'default B'}),
870+
expect.objectContaining({value: 'default B'}),
871+
]);
872+
});
873+
836874
it('should inspect forwardRef', async () => {
837875
const obj = function () {};
838876
const Foo = React.forwardRef(function (props, ref) {

0 commit comments

Comments
 (0)