@samitouri / QOS-React-1 / commits / 56cd10beb4

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)

Sebastian Silbermann committed Feb 5, 2024 at 15:39 UTC 56cd10beb40586d09e91157e8f6ac531478a62be
3 files changed +83 -1
packages/react-debug-tools/src/ReactDebugHooks.js
+27 -1
@@ -8,6 +8,7 @@
8 */
9
10 import type {
11 + Awaited,
12 ReactContext,
13 ReactProviderType,
14 StartTransitionOptions,
@@ -80,11 +81,14 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
81 // This type check is for Flow only.
82 Dispatcher.useMemoCache(0);
83 }
83 -
84 if (typeof Dispatcher.useOptimistic === 'function') {
85 // This type check is for Flow only.
86 Dispatcher.useOptimistic(null, (s: mixed, a: mixed) => s);
87 }
88 + if (typeof Dispatcher.useFormState === 'function') {
89 + // This type check is for Flow only.
90 + Dispatcher.useFormState((s: mixed, p: mixed) => s, null);
91 + }
92 } finally {
93 readHookLog = hookLog;
94 hookLog = [];
@@ -372,6 +376,27 @@ function useOptimistic<S, A>(
376 return [state, (action: A) => {}];
377 }
378
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 +
400 const Dispatcher: DispatcherType = {
401 use,
402 readContext,
@@ -393,6 +418,7 @@ const Dispatcher: DispatcherType = {
418 useSyncExternalStore,
419 useDeferredValue,
420 useId,
421 + useFormState,
422 };
423
424 // create a proxy to throw a custom error
packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js
+42
@@ -11,6 +11,7 @@
11 'use strict';
12
13 let React;
14 +let ReactDOM;
15 let ReactTestRenderer;
16 let ReactDebugTools;
17 let act;
@@ -21,6 +22,7 @@ describe('ReactHooksInspectionIntegration', () => {
22 jest.resetModules();
23 React = require('react');
24 ReactTestRenderer = require('react-test-renderer');
25 + ReactDOM = require('react-dom');
26 act = require('internal-test-utils').act;
27 ReactDebugTools = require('react-debug-tools');
28 useMemoCache = React.unstable_useMemoCache;
@@ -1144,4 +1146,44 @@ describe('ReactHooksInspectionIntegration', () => {
1146 },
1147 ]);
1148 });
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 + });
1189 });
packages/react-devtools-shell/src/app/InspectableElements/CustomHooks.js
+14
@@ -20,6 +20,7 @@ import {
20 useOptimistic,
21 useState,
22 } from 'react';
23 +import {useFormState} from 'react-dom';
24
25 const object = {
26 string: 'abc',
@@ -117,6 +118,18 @@ function wrapWithHoc(Component: (props: any, ref: React$Ref<any>) => any) {
118 }
119 const HocWithHooks = wrapWithHoc(FunctionWithHooks);
120
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 +
133 export default function CustomHooks(): React.Node {
134 return (
135 <Fragment>
@@ -124,6 +137,7 @@ export default function CustomHooks(): React.Node {
137 <MemoWithHooks />
138 <ForwardRefWithHooks />
139 <HocWithHooks />
140 + <Forms />
141 </Fragment>
142 );
143 }