diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 446ac8463106b..68ac2587be3d0 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -427,16 +427,19 @@ function useTransition(): [ // useTransition() composes multiple hooks internally. // Advance the current hook index the same number of times // so that subsequent hooks have the right memoized state. - nextHook(); // State + const stateHook = nextHook(); nextHook(); // Callback + + const isPending = stateHook !== null ? stateHook.memoizedState : false; + hookLog.push({ displayName: null, primitive: 'Transition', stackError: new Error(), - value: undefined, + value: isPending, debugInfo: null, }); - return [false, callback => {}]; + return [isPending, () => {}]; } function useDeferredValue(value: T, initialValue?: T): T { diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js index af17f523a5e41..97110f682d572 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js @@ -952,7 +952,7 @@ describe('ReactHooksInspectionIntegration', () => { "isStateEditable": false, "name": "Transition", "subHooks": [], - "value": undefined, + "value": false, }, { "debugInfo": null, @@ -986,6 +986,168 @@ describe('ReactHooksInspectionIntegration', () => { `); }); + it('should update isPending returned from useTransition', async () => { + const IndefiniteSuspender = React.lazy(() => new Promise(() => {})); + let startTransition; + function Foo(props) { + const [show, setShow] = React.useState(false); + const [isPending, _startTransition] = React.useTransition(); + React.useMemo(() => 'hello', []); + React.useMemo(() => 'not used', []); + + // Otherwise we capture the version from the react-debug-tools dispatcher. + if (startTransition === undefined) { + startTransition = () => { + _startTransition(() => { + setShow(true); + }); + }; + } + + return ( + + {isPending ? 'Pending' : null} + {show ? : null} + + ); + } + const renderer = await act(() => { + return ReactTestRenderer.create(, {isConcurrent: true}); + }); + expect(renderer).toMatchRenderedOutput(null); + let childFiber = renderer.root.findByType(Foo)._currentFiber(); + let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); + expect(normalizeSourceLoc(tree)).toMatchInlineSnapshot(` + [ + { + "debugInfo": null, + "hookSource": { + "columnNumber": 0, + "fileName": "**", + "functionName": "Foo", + "lineNumber": 0, + }, + "id": 0, + "isStateEditable": true, + "name": "State", + "subHooks": [], + "value": false, + }, + { + "debugInfo": null, + "hookSource": { + "columnNumber": 0, + "fileName": "**", + "functionName": "Foo", + "lineNumber": 0, + }, + "id": 1, + "isStateEditable": false, + "name": "Transition", + "subHooks": [], + "value": false, + }, + { + "debugInfo": null, + "hookSource": { + "columnNumber": 0, + "fileName": "**", + "functionName": "Foo", + "lineNumber": 0, + }, + "id": 2, + "isStateEditable": false, + "name": "Memo", + "subHooks": [], + "value": "hello", + }, + { + "debugInfo": null, + "hookSource": { + "columnNumber": 0, + "fileName": "**", + "functionName": "Foo", + "lineNumber": 0, + }, + "id": 3, + "isStateEditable": false, + "name": "Memo", + "subHooks": [], + "value": "not used", + }, + ] + `); + + await act(() => { + startTransition(); + }); + + expect(renderer).toMatchRenderedOutput('Pending'); + + childFiber = renderer.root.findByType(Foo)._currentFiber(); + tree = ReactDebugTools.inspectHooksOfFiber(childFiber); + expect(normalizeSourceLoc(tree)).toMatchInlineSnapshot(` + [ + { + "debugInfo": null, + "hookSource": { + "columnNumber": 0, + "fileName": "**", + "functionName": "Foo", + "lineNumber": 0, + }, + "id": 0, + "isStateEditable": true, + "name": "State", + "subHooks": [], + "value": false, + }, + { + "debugInfo": null, + "hookSource": { + "columnNumber": 0, + "fileName": "**", + "functionName": "Foo", + "lineNumber": 0, + }, + "id": 1, + "isStateEditable": false, + "name": "Transition", + "subHooks": [], + "value": true, + }, + { + "debugInfo": null, + "hookSource": { + "columnNumber": 0, + "fileName": "**", + "functionName": "Foo", + "lineNumber": 0, + }, + "id": 2, + "isStateEditable": false, + "name": "Memo", + "subHooks": [], + "value": "hello", + }, + { + "debugInfo": null, + "hookSource": { + "columnNumber": 0, + "fileName": "**", + "functionName": "Foo", + "lineNumber": 0, + }, + "id": 3, + "isStateEditable": false, + "name": "Memo", + "subHooks": [], + "value": "not used", + }, + ] + `); + }); + it('should support useDeferredValue hook', () => { function Foo(props) { React.useDeferredValue('abc'); diff --git a/packages/react-devtools-shell/src/app/InspectableElements/CustomHooks.js b/packages/react-devtools-shell/src/app/InspectableElements/CustomHooks.js index 2a845ec12e4e9..c8fbca78a9dea 100644 --- a/packages/react-devtools-shell/src/app/InspectableElements/CustomHooks.js +++ b/packages/react-devtools-shell/src/app/InspectableElements/CustomHooks.js @@ -120,6 +120,34 @@ function wrapWithHoc(Component: (props: any, ref: React$Ref) => any) { } const HocWithHooks = wrapWithHoc(FunctionWithHooks); +const Suspendender = React.lazy(() => { + return new Promise(resolve => { + setTimeout(() => { + resolve({ + default: () => 'Finished!', + }); + }, 3000); + }); +}); +function Transition() { + const [show, setShow] = React.useState(false); + const [isPending, startTransition] = React.useTransition(); + + return ( +
+ + {isPending ? 'Pending' : null} + {show ? : null} + + {!show && ( + + )} +
+ ); +} + function incrementWithDelay(previousState: number, formData: FormData) { const incrementDelay = +formData.get('incrementDelay'); const shouldReject = formData.get('shouldReject'); @@ -183,6 +211,7 @@ export default function CustomHooks(): React.Node { +