Skip to content

Commit 56cd10b

Browse files
authored
DevTools: Add support for useFormState (#28232)
## Summary Add support for `useFormState` Hook fixing "Unsupported hook in the react-debug-tools package: Missing method in Dispatcher: useFormState" when inspecting components using `useFormState` ## How did you test this change? - Added test to ReactHooksInspectionIntegration - Added dedicated section for form actions to devtools-shell ![Screenshot 2024-02-04 at 12 02 05](https://github.com/facebook/react/assets/12292047/bb274789-64b8-4594-963e-87c4b6962144)
1 parent 214fe84 commit 56cd10b

File tree

3 files changed

+83
-1
lines changed

3 files changed

+83
-1
lines changed

packages/react-debug-tools/src/ReactDebugHooks.js

+27-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
import type {
11+
Awaited,
1112
ReactContext,
1213
ReactProviderType,
1314
StartTransitionOptions,
@@ -80,11 +81,14 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
8081
// This type check is for Flow only.
8182
Dispatcher.useMemoCache(0);
8283
}
83-
8484
if (typeof Dispatcher.useOptimistic === 'function') {
8585
// This type check is for Flow only.
8686
Dispatcher.useOptimistic(null, (s: mixed, a: mixed) => s);
8787
}
88+
if (typeof Dispatcher.useFormState === 'function') {
89+
// This type check is for Flow only.
90+
Dispatcher.useFormState((s: mixed, p: mixed) => s, null);
91+
}
8892
} finally {
8993
readHookLog = hookLog;
9094
hookLog = [];
@@ -372,6 +376,27 @@ function useOptimistic<S, A>(
372376
return [state, (action: A) => {}];
373377
}
374378

379+
function useFormState<S, P>(
380+
action: (Awaited<S>, P) => S,
381+
initialState: Awaited<S>,
382+
permalink?: string,
383+
): [Awaited<S>, (P) => void] {
384+
const hook = nextHook(); // FormState
385+
nextHook(); // ActionQueue
386+
let state;
387+
if (hook !== null) {
388+
state = hook.memoizedState;
389+
} else {
390+
state = initialState;
391+
}
392+
hookLog.push({
393+
primitive: 'FormState',
394+
stackError: new Error(),
395+
value: state,
396+
});
397+
return [state, (payload: P) => {}];
398+
}
399+
375400
const Dispatcher: DispatcherType = {
376401
use,
377402
readContext,
@@ -393,6 +418,7 @@ const Dispatcher: DispatcherType = {
393418
useSyncExternalStore,
394419
useDeferredValue,
395420
useId,
421+
useFormState,
396422
};
397423

398424
// create a proxy to throw a custom error

packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js

+42
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
'use strict';
1212

1313
let React;
14+
let ReactDOM;
1415
let ReactTestRenderer;
1516
let ReactDebugTools;
1617
let act;
@@ -21,6 +22,7 @@ describe('ReactHooksInspectionIntegration', () => {
2122
jest.resetModules();
2223
React = require('react');
2324
ReactTestRenderer = require('react-test-renderer');
25+
ReactDOM = require('react-dom');
2426
act = require('internal-test-utils').act;
2527
ReactDebugTools = require('react-debug-tools');
2628
useMemoCache = React.unstable_useMemoCache;
@@ -1144,4 +1146,44 @@ describe('ReactHooksInspectionIntegration', () => {
11441146
},
11451147
]);
11461148
});
1149+
1150+
// @gate enableFormActions && enableAsyncActions
1151+
it('should support useFormState hook', () => {
1152+
function Foo() {
1153+
const [value] = ReactDOM.useFormState(function increment(n) {
1154+
return n;
1155+
}, 0);
1156+
React.useMemo(() => 'memo', []);
1157+
React.useMemo(() => 'not used', []);
1158+
1159+
return value;
1160+
}
1161+
1162+
const renderer = ReactTestRenderer.create(<Foo />);
1163+
const childFiber = renderer.root.findByType(Foo)._currentFiber();
1164+
const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
1165+
expect(tree).toEqual([
1166+
{
1167+
id: 0,
1168+
isStateEditable: false,
1169+
name: 'FormState',
1170+
value: 0,
1171+
subHooks: [],
1172+
},
1173+
{
1174+
id: 1,
1175+
isStateEditable: false,
1176+
name: 'Memo',
1177+
value: 'memo',
1178+
subHooks: [],
1179+
},
1180+
{
1181+
id: 2,
1182+
isStateEditable: false,
1183+
name: 'Memo',
1184+
value: 'not used',
1185+
subHooks: [],
1186+
},
1187+
]);
1188+
});
11471189
});

packages/react-devtools-shell/src/app/InspectableElements/CustomHooks.js

+14
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
useOptimistic,
2121
useState,
2222
} from 'react';
23+
import {useFormState} from 'react-dom';
2324

2425
const object = {
2526
string: 'abc',
@@ -117,13 +118,26 @@ function wrapWithHoc(Component: (props: any, ref: React$Ref<any>) => any) {
117118
}
118119
const HocWithHooks = wrapWithHoc(FunctionWithHooks);
119120

121+
function Forms() {
122+
const [state, formAction] = useFormState((n: number, formData: FormData) => {
123+
return n + 1;
124+
}, 0);
125+
return (
126+
<form>
127+
{state}
128+
<button formAction={formAction}>Increment</button>
129+
</form>
130+
);
131+
}
132+
120133
export default function CustomHooks(): React.Node {
121134
return (
122135
<Fragment>
123136
<FunctionWithHooks />
124137
<MemoWithHooks />
125138
<ForwardRefWithHooks />
126139
<HocWithHooks />
140+
<Forms />
127141
</Fragment>
128142
);
129143
}

0 commit comments

Comments
 (0)