Skip to content

Commit

Permalink
react-debug-tools accepts currentDispatcher ref as param (facebook#14556
Browse files Browse the repository at this point in the history
)

* react-debug-tools accepts currentDispatcher ref as param

* ReactDebugHooks injected dispatcher ref is optional
  • Loading branch information
bvaughn authored and jetoneza committed Jan 23, 2019
1 parent fa8a6ec commit f93a50d
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 10 deletions.
42 changes: 32 additions & 10 deletions packages/react-debug-tools/src/ReactDebugHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
ForwardRef,
} from 'shared/ReactWorkTags';

const ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
type CurrentDispatcherRef = typeof ReactSharedInternals.ReactCurrentDispatcher;

// Used to track hooks called during a render

Expand Down Expand Up @@ -408,18 +408,25 @@ function buildTree(rootStack, readHookLog): HooksTree {
export function inspectHooks<Props>(
renderFunction: Props => React$Node,
props: Props,
currentDispatcher: ?CurrentDispatcherRef,
): HooksTree {
let previousDispatcher = ReactCurrentDispatcher.current;
// DevTools will pass the current renderer's injected dispatcher.
// Other apps might compile debug hooks as part of their app though.
if (currentDispatcher == null) {
currentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
}

let previousDispatcher = currentDispatcher.current;
let readHookLog;
ReactCurrentDispatcher.current = Dispatcher;
currentDispatcher.current = Dispatcher;
let ancestorStackError;
try {
ancestorStackError = new Error();
renderFunction(props);
} finally {
readHookLog = hookLog;
hookLog = [];
ReactCurrentDispatcher.current = previousDispatcher;
currentDispatcher.current = previousDispatcher;
}
let rootStack = ErrorStackParser.parse(ancestorStackError);
return buildTree(rootStack, readHookLog);
Expand Down Expand Up @@ -450,18 +457,19 @@ function inspectHooksOfForwardRef<Props, Ref>(
renderFunction: (Props, Ref) => React$Node,
props: Props,
ref: Ref,
currentDispatcher: CurrentDispatcherRef,
): HooksTree {
let previousDispatcher = ReactCurrentDispatcher.current;
let previousDispatcher = currentDispatcher.current;
let readHookLog;
ReactCurrentDispatcher.current = Dispatcher;
currentDispatcher.current = Dispatcher;
let ancestorStackError;
try {
ancestorStackError = new Error();
renderFunction(props, ref);
} finally {
readHookLog = hookLog;
hookLog = [];
ReactCurrentDispatcher.current = previousDispatcher;
currentDispatcher.current = previousDispatcher;
}
let rootStack = ErrorStackParser.parse(ancestorStackError);
return buildTree(rootStack, readHookLog);
Expand All @@ -482,7 +490,16 @@ function resolveDefaultProps(Component, baseProps) {
return baseProps;
}

export function inspectHooksOfFiber(fiber: Fiber) {
export function inspectHooksOfFiber(
fiber: Fiber,
currentDispatcher: ?CurrentDispatcherRef,
) {
// DevTools will pass the current renderer's injected dispatcher.
// Other apps might compile debug hooks as part of their app though.
if (currentDispatcher == null) {
currentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
}

if (
fiber.tag !== FunctionComponent &&
fiber.tag !== SimpleMemoComponent &&
Expand All @@ -506,9 +523,14 @@ export function inspectHooksOfFiber(fiber: Fiber) {
try {
setupContexts(contextMap, fiber);
if (fiber.tag === ForwardRef) {
return inspectHooksOfForwardRef(type.render, props, fiber.ref);
return inspectHooksOfForwardRef(
type.render,
props,
fiber.ref,
currentDispatcher,
);
}
return inspectHooks(type, props);
return inspectHooks(type, props, currentDispatcher);
} finally {
currentHook = null;
restoreContexts(contextMap);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,4 +216,37 @@ describe('ReactHooksInspection', () => {
},
]);
});

it('should support an injected dispatcher', () => {
function Foo(props) {
let [state] = React.useState('hello world');
return <div>{state}</div>;
}

let initial = {};
let current = initial;
let getterCalls = 0;
let setterCalls = [];
let FakeDispatcherRef = {
get current() {
getterCalls++;
return current;
},
set current(value) {
setterCalls.push(value);
current = value;
},
};

expect(() => {
ReactDebugTools.inspectHooks(Foo, {}, FakeDispatcherRef);
}).toThrow(
'Hooks can only be called inside the body of a function component.',
);

expect(getterCalls).toBe(1);
expect(setterCalls).toHaveLength(2);
expect(setterCalls[0]).not.toBe(initial);
expect(setterCalls[1]).toBe(initial);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -241,4 +241,39 @@ describe('ReactHooksInspectionIntergration', () => {
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
expect(tree).toEqual([{name: 'State', value: 'def', subHooks: []}]);
});

it('should support an injected dispatcher', () => {
function Foo(props) {
let [state] = React.useState('hello world');
return <div>{state}</div>;
}

let initial = {};
let current = initial;
let getterCalls = 0;
let setterCalls = [];
let FakeDispatcherRef = {
get current() {
getterCalls++;
return current;
},
set current(value) {
setterCalls.push(value);
current = value;
},
};

let renderer = ReactTestRenderer.create(<Foo />);
let childFiber = renderer.root._currentFiber();
expect(() => {
ReactDebugTools.inspectHooksOfFiber(childFiber, FakeDispatcherRef);
}).toThrow(
'Hooks can only be called inside the body of a function component.',
);

expect(getterCalls).toBe(1);
expect(setterCalls).toHaveLength(2);
expect(setterCalls[0]).not.toBe(initial);
expect(setterCalls[1]).toBe(initial);
});
});

0 comments on commit f93a50d

Please sign in to comment.