Skip to content

Commit

Permalink
Devtools: Display actual pending state when inspecting useTransition (
Browse files Browse the repository at this point in the history
  • Loading branch information
eps1lon committed Mar 6, 2024
1 parent c11b196 commit 0066e0b
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 4 deletions.
9 changes: 6 additions & 3 deletions packages/react-debug-tools/src/ReactDebugHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(value: T, initialValue?: T): T {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -952,7 +952,7 @@ describe('ReactHooksInspectionIntegration', () => {
"isStateEditable": false,
"name": "Transition",
"subHooks": [],
"value": undefined,
"value": false,
},
{
"debugInfo": null,
Expand Down Expand Up @@ -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 (
<React.Suspense fallback="Loading">
{isPending ? 'Pending' : null}
{show ? <IndefiniteSuspender /> : null}
</React.Suspense>
);
}
const renderer = await act(() => {
return ReactTestRenderer.create(<Foo />, {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');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,34 @@ function wrapWithHoc(Component: (props: any, ref: React$Ref<any>) => any) {
}
const HocWithHooks = wrapWithHoc(FunctionWithHooks);

const Suspendender = React.lazy(() => {
return new Promise<any>(resolve => {
setTimeout(() => {
resolve({
default: () => 'Finished!',
});
}, 3000);
});
});
function Transition() {
const [show, setShow] = React.useState(false);
const [isPending, startTransition] = React.useTransition();

return (
<div>
<React.Suspense fallback="Loading">
{isPending ? 'Pending' : null}
{show ? <Suspendender /> : null}
</React.Suspense>
{!show && (
<button onClick={() => startTransition(() => setShow(true))}>
Transition
</button>
)}
</div>
);
}

function incrementWithDelay(previousState: number, formData: FormData) {
const incrementDelay = +formData.get('incrementDelay');
const shouldReject = formData.get('shouldReject');
Expand Down Expand Up @@ -183,6 +211,7 @@ export default function CustomHooks(): React.Node {
<MemoWithHooks />
<ForwardRefWithHooks />
<HocWithHooks />
<Transition />
<ErrorBoundary>
<Forms />
</ErrorBoundary>
Expand Down

0 comments on commit 0066e0b

Please sign in to comment.