Skip to content

Commit 721f16c

Browse files
author
Brian Vaughn
committed
ReactDebugHooks injected dispatcher ref is optional
1 parent 6f332dc commit 721f16c

File tree

3 files changed

+100
-51
lines changed

3 files changed

+100
-51
lines changed

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

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -406,10 +406,16 @@ function buildTree(rootStack, readHookLog): HooksTree {
406406
}
407407

408408
export function inspectHooks<Props>(
409-
currentDispatcher: CurrentDispatcherRef,
410409
renderFunction: Props => React$Node,
411410
props: Props,
411+
currentDispatcher: ?CurrentDispatcherRef,
412412
): HooksTree {
413+
// DevTools will pass the current renderer's injected dispatcher.
414+
// Other apps might compile debug hooks as part of their app though.
415+
if (currentDispatcher == null) {
416+
currentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
417+
}
418+
413419
let previousDispatcher = currentDispatcher.current;
414420
let readHookLog;
415421
currentDispatcher.current = Dispatcher;
@@ -448,10 +454,10 @@ function restoreContexts(contextMap: Map<ReactContext<any>, any>) {
448454
}
449455

450456
function inspectHooksOfForwardRef<Props, Ref>(
451-
currentDispatcher: CurrentDispatcherRef,
452457
renderFunction: (Props, Ref) => React$Node,
453458
props: Props,
454459
ref: Ref,
460+
currentDispatcher: CurrentDispatcherRef,
455461
): HooksTree {
456462
let previousDispatcher = currentDispatcher.current;
457463
let readHookLog;
@@ -485,9 +491,15 @@ function resolveDefaultProps(Component, baseProps) {
485491
}
486492

487493
export function inspectHooksOfFiber(
488-
currentDispatcher: CurrentDispatcherRef,
489494
fiber: Fiber,
495+
currentDispatcher: ?CurrentDispatcherRef,
490496
) {
497+
// DevTools will pass the current renderer's injected dispatcher.
498+
// Other apps might compile debug hooks as part of their app though.
499+
if (currentDispatcher == null) {
500+
currentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
501+
}
502+
491503
if (
492504
fiber.tag !== FunctionComponent &&
493505
fiber.tag !== SimpleMemoComponent &&
@@ -512,13 +524,13 @@ export function inspectHooksOfFiber(
512524
setupContexts(contextMap, fiber);
513525
if (fiber.tag === ForwardRef) {
514526
return inspectHooksOfForwardRef(
515-
currentDispatcher,
516527
type.render,
517528
props,
518529
fiber.ref,
530+
currentDispatcher,
519531
);
520532
}
521-
return inspectHooks(currentDispatcher, type, props);
533+
return inspectHooks(type, props, currentDispatcher);
522534
} finally {
523535
currentHook = null;
524536
restoreContexts(contextMap);

packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.internal.js

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
let React;
1414
let ReactDebugTools;
15-
let currentDispatcher;
1615

1716
describe('ReactHooksInspection', () => {
1817
beforeEach(() => {
@@ -22,18 +21,14 @@ describe('ReactHooksInspection', () => {
2221
ReactFeatureFlags.enableHooks = true;
2322
React = require('react');
2423
ReactDebugTools = require('react-debug-tools');
25-
26-
currentDispatcher =
27-
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
28-
.ReactCurrentDispatcher;
2924
});
3025

3126
it('should inspect a simple useState hook', () => {
3227
function Foo(props) {
3328
let [state] = React.useState('hello world');
3429
return <div>{state}</div>;
3530
}
36-
let tree = ReactDebugTools.inspectHooks(currentDispatcher, Foo, {});
31+
let tree = ReactDebugTools.inspectHooks(Foo, {});
3732
expect(tree).toEqual([
3833
{
3934
name: 'State',
@@ -52,7 +47,7 @@ describe('ReactHooksInspection', () => {
5247
let value = useCustom('hello world');
5348
return <div>{value}</div>;
5449
}
55-
let tree = ReactDebugTools.inspectHooks(currentDispatcher, Foo, {});
50+
let tree = ReactDebugTools.inspectHooks(Foo, {});
5651
expect(tree).toEqual([
5752
{
5853
name: 'Custom',
@@ -84,7 +79,7 @@ describe('ReactHooksInspection', () => {
8479
</div>
8580
);
8681
}
87-
let tree = ReactDebugTools.inspectHooks(currentDispatcher, Foo, {});
82+
let tree = ReactDebugTools.inspectHooks(Foo, {});
8883
expect(tree).toEqual([
8984
{
9085
name: 'Custom',
@@ -147,7 +142,7 @@ describe('ReactHooksInspection', () => {
147142
</div>
148143
);
149144
}
150-
let tree = ReactDebugTools.inspectHooks(currentDispatcher, Foo, {});
145+
let tree = ReactDebugTools.inspectHooks(Foo, {});
151146
expect(tree).toEqual([
152147
{
153148
name: 'Bar',
@@ -212,7 +207,7 @@ describe('ReactHooksInspection', () => {
212207
let value = React.useContext(MyContext);
213208
return <div>{value}</div>;
214209
}
215-
let tree = ReactDebugTools.inspectHooks(currentDispatcher, Foo, {});
210+
let tree = ReactDebugTools.inspectHooks(Foo, {});
216211
expect(tree).toEqual([
217212
{
218213
name: 'Context',
@@ -221,4 +216,37 @@ describe('ReactHooksInspection', () => {
221216
},
222217
]);
223218
});
219+
220+
it('should support an injected dispatcher', () => {
221+
function Foo(props) {
222+
let [state] = React.useState('hello world');
223+
return <div>{state}</div>;
224+
}
225+
226+
let initial = {};
227+
let current = initial;
228+
let getterCalls = 0;
229+
let setterCalls = [];
230+
let FakeDispatcherRef = {
231+
get current() {
232+
getterCalls++;
233+
return current;
234+
},
235+
set current(value) {
236+
setterCalls.push(value);
237+
current = value;
238+
},
239+
};
240+
241+
expect(() => {
242+
ReactDebugTools.inspectHooks(Foo, {}, FakeDispatcherRef);
243+
}).toThrow(
244+
'Hooks can only be called inside the body of a function component.',
245+
);
246+
247+
expect(getterCalls).toBe(1);
248+
expect(setterCalls).toHaveLength(2);
249+
expect(setterCalls[0]).not.toBe(initial);
250+
expect(setterCalls[1]).toBe(initial);
251+
});
224252
});

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

Lines changed: 45 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
let React;
1414
let ReactTestRenderer;
1515
let ReactDebugTools;
16-
let currentDispatcher;
1716

1817
describe('ReactHooksInspectionIntergration', () => {
1918
beforeEach(() => {
@@ -24,10 +23,6 @@ describe('ReactHooksInspectionIntergration', () => {
2423
React = require('react');
2524
ReactTestRenderer = require('react-test-renderer');
2625
ReactDebugTools = require('react-debug-tools');
27-
28-
currentDispatcher =
29-
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
30-
.ReactCurrentDispatcher;
3126
});
3227

3328
it('should inspect the current state of useState hooks', () => {
@@ -44,10 +39,7 @@ describe('ReactHooksInspectionIntergration', () => {
4439
let renderer = ReactTestRenderer.create(<Foo prop="prop" />);
4540

4641
let childFiber = renderer.root.findByType(Foo)._currentFiber();
47-
let tree = ReactDebugTools.inspectHooksOfFiber(
48-
currentDispatcher,
49-
childFiber,
50-
);
42+
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
5143
expect(tree).toEqual([
5244
{name: 'State', value: 'hello', subHooks: []},
5345
{name: 'State', value: 'world', subHooks: []},
@@ -61,7 +53,7 @@ describe('ReactHooksInspectionIntergration', () => {
6153
setStateA('Hi');
6254

6355
childFiber = renderer.root.findByType(Foo)._currentFiber();
64-
tree = ReactDebugTools.inspectHooksOfFiber(currentDispatcher, childFiber);
56+
tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
6557

6658
expect(tree).toEqual([
6759
{name: 'State', value: 'Hi', subHooks: []},
@@ -71,7 +63,7 @@ describe('ReactHooksInspectionIntergration', () => {
7163
setStateB('world!');
7264

7365
childFiber = renderer.root.findByType(Foo)._currentFiber();
74-
tree = ReactDebugTools.inspectHooksOfFiber(currentDispatcher, childFiber);
66+
tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
7567

7668
expect(tree).toEqual([
7769
{name: 'State', value: 'Hi', subHooks: []},
@@ -119,10 +111,7 @@ describe('ReactHooksInspectionIntergration', () => {
119111

120112
let {onClick: updateStates} = renderer.root.findByType('div').props;
121113

122-
let tree = ReactDebugTools.inspectHooksOfFiber(
123-
currentDispatcher,
124-
childFiber,
125-
);
114+
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
126115
expect(tree).toEqual([
127116
{name: 'State', value: 'a', subHooks: []},
128117
{name: 'Reducer', value: 'b', subHooks: []},
@@ -137,7 +126,7 @@ describe('ReactHooksInspectionIntergration', () => {
137126
updateStates();
138127

139128
childFiber = renderer.root.findByType(Foo)._currentFiber();
140-
tree = ReactDebugTools.inspectHooksOfFiber(currentDispatcher, childFiber);
129+
tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
141130

142131
expect(tree).toEqual([
143132
{name: 'State', value: 'A', subHooks: []},
@@ -163,10 +152,7 @@ describe('ReactHooksInspectionIntergration', () => {
163152
</MyContext.Provider>,
164153
);
165154
let childFiber = renderer.root.findByType(Foo)._currentFiber();
166-
let tree = ReactDebugTools.inspectHooksOfFiber(
167-
currentDispatcher,
168-
childFiber,
169-
);
155+
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
170156
expect(tree).toEqual([
171157
{
172158
name: 'Context',
@@ -186,10 +172,7 @@ describe('ReactHooksInspectionIntergration', () => {
186172
let renderer = ReactTestRenderer.create(<Foo ref={ref} />);
187173

188174
let childFiber = renderer.root.findByType(Foo)._currentFiber();
189-
let tree = ReactDebugTools.inspectHooksOfFiber(
190-
currentDispatcher,
191-
childFiber,
192-
);
175+
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
193176
expect(tree).toEqual([
194177
{name: 'ImperativeHandle', value: obj, subHooks: []},
195178
]);
@@ -204,10 +187,7 @@ describe('ReactHooksInspectionIntergration', () => {
204187
let renderer = ReactTestRenderer.create(<Foo />);
205188
// TODO: Test renderer findByType is broken for memo. Have to search for the inner.
206189
let childFiber = renderer.root.findByType(InnerFoo)._currentFiber();
207-
let tree = ReactDebugTools.inspectHooksOfFiber(
208-
currentDispatcher,
209-
childFiber,
210-
);
190+
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
211191
expect(tree).toEqual([{name: 'State', value: 'hello', subHooks: []}]);
212192
});
213193

@@ -222,10 +202,7 @@ describe('ReactHooksInspectionIntergration', () => {
222202
}
223203
let renderer = ReactTestRenderer.create(<Foo />);
224204
let childFiber = renderer.root.findByType(Foo)._currentFiber();
225-
let tree = ReactDebugTools.inspectHooksOfFiber(
226-
currentDispatcher,
227-
childFiber,
228-
);
205+
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
229206
expect(tree).toEqual([
230207
{
231208
name: 'Custom',
@@ -261,10 +238,42 @@ describe('ReactHooksInspectionIntergration', () => {
261238
await LazyFoo;
262239

263240
let childFiber = renderer.root._currentFiber();
264-
let tree = ReactDebugTools.inspectHooksOfFiber(
265-
currentDispatcher,
266-
childFiber,
267-
);
241+
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
268242
expect(tree).toEqual([{name: 'State', value: 'def', subHooks: []}]);
269243
});
244+
245+
it('should support an injected dispatcher', () => {
246+
function Foo(props) {
247+
let [state] = React.useState('hello world');
248+
return <div>{state}</div>;
249+
}
250+
251+
let initial = {};
252+
let current = initial;
253+
let getterCalls = 0;
254+
let setterCalls = [];
255+
let FakeDispatcherRef = {
256+
get current() {
257+
getterCalls++;
258+
return current;
259+
},
260+
set current(value) {
261+
setterCalls.push(value);
262+
current = value;
263+
},
264+
};
265+
266+
let renderer = ReactTestRenderer.create(<Foo />);
267+
let childFiber = renderer.root._currentFiber();
268+
expect(() => {
269+
ReactDebugTools.inspectHooksOfFiber(childFiber, FakeDispatcherRef);
270+
}).toThrow(
271+
'Hooks can only be called inside the body of a function component.',
272+
);
273+
274+
expect(getterCalls).toBe(1);
275+
expect(setterCalls).toHaveLength(2);
276+
expect(setterCalls[0]).not.toBe(initial);
277+
expect(setterCalls[1]).toBe(initial);
278+
});
270279
});

0 commit comments

Comments
 (0)