Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

react-debug-tools accepts currentDispatcher ref as param #14556

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
});
});