DevTools: Fix inspecting components with multiple reads of the same Context in React 17 (#28974)
Sebastian Silbermann committed
May 2, 2024 at 22:08 UTC
f5f2799a8d0b37487c9674159846cbb7696febd3
2 files changed
+52
-8
packages/react-debug-tools/src/ReactDebugHooks.js
+13
-8
@@ -177,15 +177,20 @@ function readContext<T>(context: ReactContext<T>): T {
177
);
178
}
179
180
+ let value: T;
181
// 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
+ }
194
195
return value;
196
}
packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js
+39
@@ -833,6 +833,45 @@ describe('ReactHooksInspectionIntegration', () => {
833
`);
834
});
835
836
+ // @reactVersion >= 16.8
837
+ it('should inspect the value of the current provider in useContext reading the same context multiple times', async () => {
838
+ const ContextA = React.createContext('default A');
839
+ const ContextB = React.createContext('default B');
840
+ function Foo(props) {
841
+ React.useContext(ContextA);
842
+ React.useContext(ContextA);
843
+ React.useContext(ContextB);
844
+ React.useContext(ContextB);
845
+ React.useContext(ContextA);
846
+ React.useContext(ContextB);
847
+ React.useContext(ContextB);
848
+ React.useContext(ContextB);
849
+ return null;
850
+ }
851
+ let renderer;
852
+ await act(() => {
853
+ renderer = ReactTestRenderer.create(
854
+ <ContextA.Provider value="contextual A">
855
+ <Foo prop="prop" />
856
+ </ContextA.Provider>,
857
+ {unstable_isConcurrent: true},
858
+ );
859
+ });
860
+ const childFiber = renderer.root.findByType(Foo)._currentFiber();
861
+ const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
862
+
863
+ expect(normalizeSourceLoc(tree)).toEqual([
864
+ expect.objectContaining({value: 'contextual A'}),
865
+ expect.objectContaining({value: 'contextual A'}),
866
+ expect.objectContaining({value: 'default B'}),
867
+ expect.objectContaining({value: 'default B'}),
868
+ expect.objectContaining({value: 'contextual A'}),
869
+ expect.objectContaining({value: 'default B'}),
870
+ expect.objectContaining({value: 'default B'}),
871
+ expect.objectContaining({value: 'default B'}),
872
+ ]);
873
+ });
874
+
875
it('should inspect forwardRef', async () => {
876
const obj = function () {};
877
const Foo = React.forwardRef(function (props, ref) {