From 099097f1cc8ea919491f54cc538376ece724f41e Mon Sep 17 00:00:00 2001 From: Raashish Aggarwal <94279692+raashish1601@users.noreply.github.com> Date: Sat, 2 May 2026 10:16:06 +0530 Subject: [PATCH] Restore controlled form state after reset --- .../src/client/ReactFiberConfigDOM.js | 20 ++++++++ .../src/__tests__/ReactDOMForm-test.js | 46 +++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index db3e2806ac3e..8ca3bb22ba7f 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -83,6 +83,7 @@ import { getPropsFromElement, diffHydratedText, trapClickOnNonInteractiveElement, + restoreControlledState, } from './ReactDOMComponent'; import {hydrateInput} from './ReactDOMInput'; import {hydrateTextarea} from './ReactDOMTextarea'; @@ -6644,8 +6645,27 @@ export const HostTransitionContext: ReactContext = { }; export type FormInstance = HTMLFormElement; + +function restoreControlledFormControlState(element: Element) { + const internalInstance = getInstanceFromNode(element); + if (internalInstance === null || internalInstance.tag !== HostComponent) { + return; + } + const stateNode = internalInstance.stateNode; + if (stateNode !== element) { + return; + } + const props = getFiberCurrentPropsFromNode(stateNode); + restoreControlledState(stateNode, internalInstance.type, props); +} + export function resetFormInstance(form: FormInstance): void { ReactBrowserEventEmitterSetEnabled(true); form.reset(); ReactBrowserEventEmitterSetEnabled(false); + + const elements = form.elements; + for (let i = 0; i < elements.length; i++) { + restoreControlledFormControlState((elements[i]: any)); + } } diff --git a/packages/react-dom/src/__tests__/ReactDOMForm-test.js b/packages/react-dom/src/__tests__/ReactDOMForm-test.js index be4a232e1ab1..4d5ad622379d 100644 --- a/packages/react-dom/src/__tests__/ReactDOMForm-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMForm-test.js @@ -1647,6 +1647,52 @@ describe('ReactDOMForm', () => { expect(inputRef.current.value).toEqual('0'); }); + it('should restore a controlled select after automatic form reset', async () => { + const formRef = React.createRef(); + const selectRef = React.createRef(); + + function App() { + const [value, setValue] = useState('2'); + + return ( +
{ + Scheduler.log(`Async action started`); + await getText('Wait'); + }}> + +
+ ); + } + + const root = ReactDOMClient.createRoot(container); + await act(() => root.render()); + + expect(selectRef.current.value).toEqual('2'); + + // Submit the form. This will trigger an async action. + await submit(formRef.current); + assertLog(['Async action started']); + + // We haven't reset yet. + expect(selectRef.current.value).toEqual('2'); + + // Action completes. The controlled select is restored after the native + // form reset runs. + await act(() => resolveText('Wait')); + assertLog([]); + expect(selectRef.current.value).toEqual('2'); + }); + it('requestFormReset schedules a form reset after transition completes', async () => { // This is the same as the previous test, except the form is updated with // a userspace action instead of a built-in form action.