From 374b5d26c2a379fe87ee6817217c8956c4e39aac Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Wed, 10 Apr 2024 16:55:15 -0400 Subject: [PATCH] Scaffolding for requestFormReset API (#28808) Based on: - #28804 --- This sets adds a new ReactDOM export called requestFormReset, including setting up the export and creating a method on the internal ReactDOM dispatcher. It does not yet add any implementation. Doing this in its own commit for review purposes. The API itself will be explained in the next PR. --- .../src/client/ReactFiberConfigDOM.js | 19 +++++++++++++++++++ .../ReactDOMFlightServerHostDispatcher.js | 1 + .../src/server/ReactFizzConfigDOM.js | 1 + .../src/shared/ReactDOMFormActions.js | 6 ++++++ packages/react-dom/index.classic.fb.js | 1 + packages/react-dom/index.experimental.js | 1 + packages/react-dom/index.js | 1 + packages/react-dom/index.modern.fb.js | 1 + packages/react-dom/index.stable.js | 1 + .../react-dom/src/ReactDOMSharedInternals.js | 8 ++++++++ .../src/ReactDOMSharedInternalsFB.js | 1 + packages/react-dom/src/client/ReactDOM.js | 1 + packages/react-dom/src/client/ReactDOMFB.js | 1 + .../react-dom/src/shared/ReactDOMTypes.js | 1 + .../react-reconciler/src/ReactFiberHooks.js | 9 +++++++-- scripts/error-codes/codes.json | 3 ++- 16 files changed, 53 insertions(+), 3 deletions(-) diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index ac76fafd5a..9e937c0138 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -102,6 +102,7 @@ import {listenToAllSupportedEvents} from '../events/DOMPluginEventSystem'; import {validateLinkPropsForStyleResource} from '../shared/ReactDOMResourceValidation'; import escapeSelectorAttributeValueInsideDoubleQuotes from './escapeSelectorAttributeValueInsideDoubleQuotes'; import {flushSyncWork as flushSyncWorkOnAllRoots} from 'react-reconciler/src/ReactFiberWorkLoop'; +import {requestFormReset as requestFormResetOnFiber} from 'react-reconciler/src/ReactFiberHooks'; import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals'; @@ -1928,6 +1929,7 @@ ReactDOMSharedInternals.d /* ReactDOMCurrentDispatcher */ = { f /* flushSyncWork */: disableLegacyMode ? flushSyncWork : previousDispatcher.f /* flushSyncWork */, + r: requestFormReset, D /* prefetchDNS */: prefetchDNS, C /* preconnect */: preconnect, L /* preload */: preload, @@ -1951,6 +1953,23 @@ function flushSyncWork() { } } +function requestFormReset(form: HTMLFormElement) { + const formInst = getInstanceFromNodeDOMTree(form); + if ( + formInst !== null && + formInst.tag === HostComponent && + formInst.type === 'form' + ) { + requestFormResetOnFiber(formInst); + } else { + // This form was either not rendered by this React renderer (or it's an + // invalid type). Try the next one. + // + // The last implementation in the sequence will throw an error. + previousDispatcher.r(/* requestFormReset */ form); + } +} + // We expect this to get inlined. It is a function mostly to communicate the special nature of // how we resolve the HoistableRoot for ReactDOM.pre*() methods. Because we support calling // these methods outside of render there is no way to know which Document or ShadowRoot is 'scoped' diff --git a/packages/react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js b/packages/react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js index 602452f67e..3303c07cfb 100644 --- a/packages/react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js +++ b/packages/react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js @@ -28,6 +28,7 @@ const previousDispatcher = ReactDOMSharedInternals.d; /* ReactDOMCurrentDispatcher */ ReactDOMSharedInternals.d /* ReactDOMCurrentDispatcher */ = { f /* flushSyncWork */: previousDispatcher.f /* flushSyncWork */, + r /* requestFormReset */: previousDispatcher.r /* requestFormReset */, D /* prefetchDNS */: prefetchDNS, C /* preconnect */: preconnect, L /* preload */: preload, diff --git a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js index f8989c18a4..7329582048 100644 --- a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js +++ b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js @@ -88,6 +88,7 @@ const previousDispatcher = ReactDOMSharedInternals.d; /* ReactDOMCurrentDispatcher */ ReactDOMSharedInternals.d /* ReactDOMCurrentDispatcher */ = { f /* flushSyncWork */: previousDispatcher.f /* flushSyncWork */, + r /* requestFormReset */: previousDispatcher.r /* requestFormReset */, D /* prefetchDNS */: prefetchDNS, C /* preconnect */: preconnect, L /* preload */: preload, diff --git a/packages/react-dom-bindings/src/shared/ReactDOMFormActions.js b/packages/react-dom-bindings/src/shared/ReactDOMFormActions.js index 42967f7ce4..0b3f478e33 100644 --- a/packages/react-dom-bindings/src/shared/ReactDOMFormActions.js +++ b/packages/react-dom-bindings/src/shared/ReactDOMFormActions.js @@ -12,6 +12,7 @@ import type {Awaited} from 'shared/ReactTypes'; import {enableAsyncActions} from 'shared/ReactFeatureFlags'; import ReactSharedInternals from 'shared/ReactSharedInternals'; +import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals'; type FormStatusNotPending = {| pending: false, @@ -87,3 +88,8 @@ export function useFormState( return dispatcher.useFormState(action, initialState, permalink); } } + +export function requestFormReset(form: HTMLFormElement) { + ReactDOMSharedInternals.d /* ReactDOMCurrentDispatcher */ + .r(/* requestFormReset */ form); +} diff --git a/packages/react-dom/index.classic.fb.js b/packages/react-dom/index.classic.fb.js index a565a91f43..63c20f7e51 100644 --- a/packages/react-dom/index.classic.fb.js +++ b/packages/react-dom/index.classic.fb.js @@ -25,6 +25,7 @@ export { unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority. useFormStatus, useFormState, + requestFormReset, prefetchDNS, preconnect, preload, diff --git a/packages/react-dom/index.experimental.js b/packages/react-dom/index.experimental.js index f2d3c27aea..0cc5ef13e3 100644 --- a/packages/react-dom/index.experimental.js +++ b/packages/react-dom/index.experimental.js @@ -17,6 +17,7 @@ export { unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority. useFormStatus, useFormState, + requestFormReset, prefetchDNS, preconnect, preload, diff --git a/packages/react-dom/index.js b/packages/react-dom/index.js index 50ad087dfa..b6f85ce628 100644 --- a/packages/react-dom/index.js +++ b/packages/react-dom/index.js @@ -20,6 +20,7 @@ export { unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority. useFormStatus, useFormState, + requestFormReset, prefetchDNS, preconnect, preload, diff --git a/packages/react-dom/index.modern.fb.js b/packages/react-dom/index.modern.fb.js index 0d2fa4b4dc..861febfa5e 100644 --- a/packages/react-dom/index.modern.fb.js +++ b/packages/react-dom/index.modern.fb.js @@ -16,6 +16,7 @@ export { unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority. useFormStatus, useFormState, + requestFormReset, prefetchDNS, preconnect, preload, diff --git a/packages/react-dom/index.stable.js b/packages/react-dom/index.stable.js index a9306083c0..f5ed138d67 100644 --- a/packages/react-dom/index.stable.js +++ b/packages/react-dom/index.stable.js @@ -16,6 +16,7 @@ export { unstable_batchedUpdates, useFormStatus, useFormState, + requestFormReset, prefetchDNS, preconnect, preload, diff --git a/packages/react-dom/src/ReactDOMSharedInternals.js b/packages/react-dom/src/ReactDOMSharedInternals.js index 50f19f563c..0a0f978667 100644 --- a/packages/react-dom/src/ReactDOMSharedInternals.js +++ b/packages/react-dom/src/ReactDOMSharedInternals.js @@ -29,8 +29,16 @@ export type ReactDOMInternalsDev = ReactDOMInternals & { function noop() {} +function requestFormReset(element: HTMLFormElement) { + throw new Error( + 'Invalid form element. requestFormReset must be passed a form that was ' + + 'rendered by React.', + ); +} + const DefaultDispatcher: HostDispatcher = { f /* flushSyncWork */: noop, + r /* requestFormReset */: requestFormReset, D /* prefetchDNS */: noop, C /* preconnect */: noop, L /* preload */: noop, diff --git a/packages/react-dom/src/ReactDOMSharedInternalsFB.js b/packages/react-dom/src/ReactDOMSharedInternalsFB.js index 7bd7a1ab8a..4e425d5ddc 100644 --- a/packages/react-dom/src/ReactDOMSharedInternalsFB.js +++ b/packages/react-dom/src/ReactDOMSharedInternalsFB.js @@ -27,6 +27,7 @@ function noop() {} const DefaultDispatcher: HostDispatcher = { f /* flushSyncWork */: noop, + r /* requestFormReset */: noop, D /* prefetchDNS */: noop, C /* preconnect */: noop, L /* preload */: noop, diff --git a/packages/react-dom/src/client/ReactDOM.js b/packages/react-dom/src/client/ReactDOM.js index 86979fa11f..61660e9843 100644 --- a/packages/react-dom/src/client/ReactDOM.js +++ b/packages/react-dom/src/client/ReactDOM.js @@ -48,6 +48,7 @@ export { export { useFormStatus, useFormState, + requestFormReset, } from 'react-dom-bindings/src/shared/ReactDOMFormActions'; if (__DEV__) { diff --git a/packages/react-dom/src/client/ReactDOMFB.js b/packages/react-dom/src/client/ReactDOMFB.js index e24cba3a52..9b016b04d8 100644 --- a/packages/react-dom/src/client/ReactDOMFB.js +++ b/packages/react-dom/src/client/ReactDOMFB.js @@ -48,6 +48,7 @@ export { export { useFormStatus, useFormState, + requestFormReset, } from 'react-dom-bindings/src/shared/ReactDOMFormActions'; if (__DEV__) { diff --git a/packages/react-dom/src/shared/ReactDOMTypes.js b/packages/react-dom/src/shared/ReactDOMTypes.js index 2da68cae2d..dbfe07f9ec 100644 --- a/packages/react-dom/src/shared/ReactDOMTypes.js +++ b/packages/react-dom/src/shared/ReactDOMTypes.js @@ -83,6 +83,7 @@ export type PreinitModuleScriptOptions = { export type HostDispatcher = { f /* flushSyncWork */: () => boolean | void, + r /* requestFormReset */: (form: HTMLFormElement) => void, D /* prefetchDNS */: (href: string) => void, C /* preconnect */: (href: string, crossOrigin?: ?CrossOriginEnum) => void, L /* preload */: ( diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index b32c9c1201..c712eb6c38 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -3008,13 +3008,18 @@ export function startHostTransition( // once more of this function is implemented. () => { // Automatically reset the form when the action completes. - requestFormReset(formFiber); + requestFormResetImpl(formFiber); return callback(formData); }, ); } -function requestFormReset(formFiber: Fiber) { +export function requestFormReset(formFiber: Fiber) { + // TODO: Not yet implemented. Need to upgrade the fiber to be stateful + // before scheduling the form reset. +} + +function requestFormResetImpl(formFiber: Fiber) { const transition = requestCurrentTransition(); if (__DEV__) { diff --git a/scripts/error-codes/codes.json b/scripts/error-codes/codes.json index 799ce858f3..2ccd0ee9f9 100644 --- a/scripts/error-codes/codes.json +++ b/scripts/error-codes/codes.json @@ -506,5 +506,6 @@ "518": "Saw multiple hydration diff roots in a pass. This is a bug in React.", "519": "Hydration Mismatch Exception: This is not a real error, and should not leak into userspace. If you're seeing this, it's likely a bug in React.", "520": "There was an error during concurrent rendering but React was able to recover by instead synchronously rendering the entire root.", - "521": "flushSyncWork should not be called from builds that support legacy mode. This is a bug in React." + "521": "flushSyncWork should not be called from builds that support legacy mode. This is a bug in React.", + "522": "Invalid form element. requestFormReset must be passed a form that was rendered by React." }