react-debug-tools accepts currentDispatcher ref as param (#14556)
* react-debug-tools accepts currentDispatcher ref as param * ReactDebugHooks injected dispatcher ref is optional
Brian Vaughn committed
Jan 10, 2019 at 12:56 UTC
f290138d329aa7aa635d88868705b23372e9f004
3 files changed
+100
-10
packages/react-debug-tools/src/ReactDebugHooks.js
+32
-10
@@ -20,7 +20,7 @@ import {
20
ForwardRef,
21
} from 'shared/ReactWorkTags';
22
23
-const ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
23
+type CurrentDispatcherRef = typeof ReactSharedInternals.ReactCurrentDispatcher;
24
25
// Used to track hooks called during a render
26
@@ -408,10 +408,17 @@ function buildTree(rootStack, readHookLog): HooksTree {
408
export function inspectHooks<Props>(
409
renderFunction: Props => React$Node,
410
props: Props,
411
+ currentDispatcher: ?CurrentDispatcherRef,
412
): HooksTree {
412
- let previousDispatcher = ReactCurrentDispatcher.current;
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
+
419
+ let previousDispatcher = currentDispatcher.current;
420
let readHookLog;
414
- ReactCurrentDispatcher.current = Dispatcher;
421
+ currentDispatcher.current = Dispatcher;
422
let ancestorStackError;
423
try {
424
ancestorStackError = new Error();
@@ -419,7 +426,7 @@ export function inspectHooks<Props>(
426
} finally {
427
readHookLog = hookLog;
428
hookLog = [];
422
- ReactCurrentDispatcher.current = previousDispatcher;
429
+ currentDispatcher.current = previousDispatcher;
430
}
431
let rootStack = ErrorStackParser.parse(ancestorStackError);
432
return buildTree(rootStack, readHookLog);
@@ -450,10 +457,11 @@ function inspectHooksOfForwardRef<Props, Ref>(
457
renderFunction: (Props, Ref) => React$Node,
458
props: Props,
459
ref: Ref,
460
+ currentDispatcher: CurrentDispatcherRef,
461
): HooksTree {
454
- let previousDispatcher = ReactCurrentDispatcher.current;
462
+ let previousDispatcher = currentDispatcher.current;
463
let readHookLog;
456
- ReactCurrentDispatcher.current = Dispatcher;
464
+ currentDispatcher.current = Dispatcher;
465
let ancestorStackError;
466
try {
467
ancestorStackError = new Error();
@@ -461,7 +469,7 @@ function inspectHooksOfForwardRef<Props, Ref>(
469
} finally {
470
readHookLog = hookLog;
471
hookLog = [];
464
- ReactCurrentDispatcher.current = previousDispatcher;
472
+ currentDispatcher.current = previousDispatcher;
473
}
474
let rootStack = ErrorStackParser.parse(ancestorStackError);
475
return buildTree(rootStack, readHookLog);
@@ -482,7 +490,16 @@ function resolveDefaultProps(Component, baseProps) {
490
return baseProps;
491
}
492
485
-export function inspectHooksOfFiber(fiber: Fiber) {
493
+export function inspectHooksOfFiber(
494
+ fiber: Fiber,
495
+ currentDispatcher: ?CurrentDispatcherRef,
496
+) {
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
+
503
if (
504
fiber.tag !== FunctionComponent &&
505
fiber.tag !== SimpleMemoComponent &&
@@ -506,9 +523,14 @@ export function inspectHooksOfFiber(fiber: Fiber) {
523
try {
524
setupContexts(contextMap, fiber);
525
if (fiber.tag === ForwardRef) {
509
- return inspectHooksOfForwardRef(type.render, props, fiber.ref);
526
+ return inspectHooksOfForwardRef(
527
+ type.render,
528
+ props,
529
+ fiber.ref,
530
+ currentDispatcher,
531
+ );
532
}
511
- return inspectHooks(type, props);
533
+ return inspectHooks(type, props, currentDispatcher);
534
} finally {
535
currentHook = null;
536
restoreContexts(contextMap);
packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.internal.js
+33
@@ -216,4 +216,37 @@ describe('ReactHooksInspection', () => {
216
},
217
]);
218
});
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
+ });
252
});
packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.internal.js
+35
@@ -241,4 +241,39 @@ describe('ReactHooksInspectionIntergration', () => {
241
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
242
expect(tree).toEqual([{name: 'State', value: 'def', subHooks: []}]);
243
});
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
+ });
279
});