Skip to content

Commit

Permalink
DevTools: Add support for useFormStatus
Browse files Browse the repository at this point in the history
  • Loading branch information
Sebastian Silbermann committed Mar 5, 2024
1 parent 0775186 commit f0d6592
Show file tree
Hide file tree
Showing 4 changed files with 227 additions and 3 deletions.
30 changes: 29 additions & 1 deletion packages/react-debug-tools/src/ReactDebugHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type {
Fiber,
Dispatcher as DispatcherType,
} from 'react-reconciler/src/ReactInternalTypes';
import type {TransitionStatus} from 'react-reconciler/src/ReactFiberConfig';

import ErrorStackParser from 'error-stack-parser';
import assign from 'shared/assign';
Expand Down Expand Up @@ -124,6 +125,11 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
);
} catch (x) {}
}

if (typeof Dispatcher.useHostTransitionStatus === 'function') {
// This type check is for Flow only.
Dispatcher.useHostTransitionStatus();
}
} finally {
readHookLog = hookLog;
hookLog = [];
Expand Down Expand Up @@ -597,6 +603,26 @@ function useFormState<S, P>(
return [state, (payload: P) => {}];
}

function useHostTransitionStatus(): TransitionStatus {
const status = readContext<TransitionStatus>(
// $FlowFixMe[prop-missing] `readContext` only needs _currentValue
({
// $FlowFixMe[incompatible-cast] TODO: Incorrect bottom value without access to Fiber config.
_currentValue: null,
}: ReactContext<TransitionStatus>),
);

hookLog.push({
displayName: null,
primitive: 'HostTransitionStatus',
stackError: new Error(),
value: status,
debugInfo: null,
});

return status;
}

const Dispatcher: DispatcherType = {
use,
readContext,
Expand All @@ -619,6 +645,7 @@ const Dispatcher: DispatcherType = {
useDeferredValue,
useId,
useFormState,
useHostTransitionStatus,
};

// create a proxy to throw a custom error
Expand Down Expand Up @@ -871,7 +898,8 @@ function buildTree(
primitive === 'Context (use)' ||
primitive === 'DebugValue' ||
primitive === 'Promise' ||
primitive === 'Unresolved'
primitive === 'Unresolved' ||
primitive === 'HostTransitionStatus'
? null
: nativeHookID++;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment jsdom
*/

'use strict';

let React;
let ReactDOM;
let ReactDOMClient;
let ReactDebugTools;
let act;

function normalizeSourceLoc(tree) {
tree.forEach(node => {
if (node.hookSource) {
node.hookSource.fileName = '**';
node.hookSource.lineNumber = 0;
node.hookSource.columnNumber = 0;
}
normalizeSourceLoc(node.subHooks);
});
return tree;
}

describe('ReactHooksInspectionIntegration', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
act = require('internal-test-utils').act;
ReactDebugTools = require('react-debug-tools');
});

// @gate enableFormActions && enableAsyncActions
it('should support useFormStatus hook', async () => {
function FormStatus() {
const status = ReactDOM.useFormStatus();
React.useMemo(() => 'memo', []);
React.useMemo(() => 'not used', []);

return JSON.stringify(status);
}

const treeWithoutFiber = ReactDebugTools.inspectHooks(FormStatus);
expect(normalizeSourceLoc(treeWithoutFiber)).toEqual([
{
debugInfo: null,
hookSource: {
columnNumber: 0,
fileName: '**',
functionName: 'FormStatus',
lineNumber: 0,
},
id: null,
isStateEditable: false,
name: '.useFormStatus',
subHooks: [
{
debugInfo: null,
hookSource: {
columnNumber: 0,
fileName: '**',
functionName: 'Object.useFormStatus',
lineNumber: 0,
},
id: null,
isStateEditable: false,
name: 'HostTransitionStatus',
subHooks: [],
value: null,
},
],
value: null,
},
{
debugInfo: null,
hookSource: {
columnNumber: 0,
fileName: '**',
functionName: 'FormStatus',
lineNumber: 0,
},
id: 0,
isStateEditable: false,
name: 'Memo',
subHooks: [],
value: 'memo',
},
{
debugInfo: null,
hookSource: {
columnNumber: 0,
fileName: '**',
functionName: 'FormStatus',
lineNumber: 0,
},
id: 1,
isStateEditable: false,
name: 'Memo',
subHooks: [],
value: 'not used',
},
]);

const root = ReactDOMClient.createRoot(document.createElement('div'));

await act(() => {
root.render(
<form>
<FormStatus />
</form>,
);
});

// Implementation detail. Feel free to adjust the position of the Fiber in the tree.
const formStatusFiber = root._internalRoot.current.child.child;
const treeWithFiber = ReactDebugTools.inspectHooksOfFiber(formStatusFiber);
expect(normalizeSourceLoc(treeWithFiber)).toEqual([
{
debugInfo: null,
hookSource: {
columnNumber: 0,
fileName: '**',
functionName: 'FormStatus',
lineNumber: 0,
},
id: null,
isStateEditable: false,
name: '.useFormStatus',
subHooks: [
{
debugInfo: null,
hookSource: {
columnNumber: 0,
fileName: '**',
functionName: 'Object.useFormStatus',
lineNumber: 0,
},
id: null,
isStateEditable: false,
name: 'HostTransitionStatus',
subHooks: [],
value: null,
},
],
value: null,
},
{
debugInfo: null,
hookSource: {
columnNumber: 0,
fileName: '**',
functionName: 'FormStatus',
lineNumber: 0,
},
id: 0,
isStateEditable: false,
name: 'Memo',
subHooks: [],
value: 'memo',
},
{
debugInfo: null,
hookSource: {
columnNumber: 0,
fileName: '**',
functionName: 'FormStatus',
lineNumber: 0,
},
id: 1,
isStateEditable: false,
name: 'Memo',
subHooks: [],
value: 'not used',
},
]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
useState,
use,
} from 'react';
import {useFormState} from 'react-dom';
import {useFormState, useFormStatus} from 'react-dom';

const object = {
string: 'abc',
Expand Down Expand Up @@ -136,6 +136,12 @@ function incrementWithDelay(previousState: number, formData: FormData) {
});
}

function FormStatus() {
const status = useFormStatus();

return <pre>{JSON.stringify(status)}</pre>;
}

function Forms() {
const [state, formAction] = useFormState<any, any>(incrementWithDelay, 0);
return (
Expand All @@ -156,6 +162,7 @@ function Forms() {
<input name="shouldReject" type="checkbox" />
</label>
<button formAction={formAction}>Increment</button>
<FormStatus />
</form>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,11 @@ export function useFormStatus(): FormStatus {
} else {
const dispatcher = resolveDispatcher();
// $FlowFixMe[not-a-function] We know this exists because of the feature check above.
return dispatcher.useHostTransitionStatus();
const status = dispatcher.useHostTransitionStatus();

dispatcher.useDebugValue(status);

return status;
}
}

Expand Down

0 comments on commit f0d6592

Please sign in to comment.