From 4d616081ba75765373b9d1e7ce6a2b5a1b439305 Mon Sep 17 00:00:00 2001 From: Sebastian Sebbie Silbermann Date: Thu, 20 Nov 2025 12:44:06 +0100 Subject: [PATCH 1/3] Current behavior --- .../src/__tests__/ReactDOMForm-test.js | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/packages/react-dom/src/__tests__/ReactDOMForm-test.js b/packages/react-dom/src/__tests__/ReactDOMForm-test.js index 96021e305aef2..0c5972e6df198 100644 --- a/packages/react-dom/src/__tests__/ReactDOMForm-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMForm-test.js @@ -1585,6 +1585,57 @@ describe('ReactDOMForm', () => { expect(divRef.current.textContent).toEqual('Current username: acdlite'); }); + it('should fire onReset on automatic form reset', async () => { + const formRef = React.createRef(); + const inputRef = React.createRef(); + + let setValue; + const defaultValue = 0; + function App({promiseForUsername}) { + const [value, _setValue] = useState(defaultValue); + setValue = _setValue; + + return ( +
{ + Scheduler.log(`Async action started`); + await getText('Wait'); + }} + onReset={() => { + setValue(defaultValue); + }}> + setValue(event.currentTarget.value)} + /> +
+ ); + } + + const root = ReactDOMClient.createRoot(container); + await act(() => root.render()); + + // Dirty the controlled input + await act(() => setValue('3')); + expect(inputRef.current.value).toEqual('3'); + + // Submit the form. This will trigger an async action. + await submit(formRef.current); + assertLog(['Async action started']); + + // We haven't reset yet. + expect(inputRef.current.value).toEqual('3'); + + // Action completes. onReset hasn't fired though. + await act(() => resolveText('Wait')); + assertLog([]); + expect(inputRef.current.value).toEqual('3'); + }); + 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. From 780e091a958a8ec287523ba54561b43fa7782576 Mon Sep 17 00:00:00 2001 From: Sebastian Sebbie Silbermann Date: Thu, 20 Nov 2025 13:15:44 +0100 Subject: [PATCH 2/3] Add fixture --- fixtures/dom/src/components/Header.js | 1 + .../components/fixtures/form-actions/index.js | 113 ++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 fixtures/dom/src/components/fixtures/form-actions/index.js diff --git a/fixtures/dom/src/components/Header.js b/fixtures/dom/src/components/Header.js index ae24b3223f643..1474d80533657 100644 --- a/fixtures/dom/src/components/Header.js +++ b/fixtures/dom/src/components/Header.js @@ -88,6 +88,7 @@ class Header extends React.Component { + diff --git a/fixtures/dom/src/components/fixtures/form-actions/index.js b/fixtures/dom/src/components/fixtures/form-actions/index.js new file mode 100644 index 0000000000000..eace38ba46beb --- /dev/null +++ b/fixtures/dom/src/components/fixtures/form-actions/index.js @@ -0,0 +1,113 @@ +const React = window.React; + +const {useState} = React; + +async function defer(timeoutMS) { + return new Promise(resolve => { + setTimeout(resolve, timeoutMS); + }); +} + +export default function FormActions() { + const [textValue, setTextValue] = useState('0'); + const [radioValue, setRadioValue] = useState('two'); + const [checkboxValue, setCheckboxValue] = useState([false, true, true]); + const [selectValue, setSelectValue] = useState('three'); + + return ( +
{ + await defer(500); + }} + onReset={() => { + setTextValue('0'); + setRadioValue('two'); + setCheckboxValue([false, true, true]); + setSelectValue('three'); + }}> +
+
+ type="text" + setTextValue(event.currentTarget.value)} + /> +
+
+ type="radio" + setRadioValue('one')} + /> + setRadioValue('two')} + /> + setRadioValue('three')} + /> +
+
+ type="checkbox" + { + const checked = event.currentTarget.checked; + setCheckboxValue(pending => [checked, pending[1], pending[2]]); + }} + /> + { + const checked = event.currentTarget.checked; + setCheckboxValue(pending => [pending[0], checked, pending[2]]); + }} + /> + { + const checked = event.currentTarget.checked; + setCheckboxValue(pending => [pending[0], pending[1], checked]); + }} + /> +
+
+ select + +
+
+
+ + +
+
+ ); +} From 16c78d7a574654a865f99e97b254801b035249a7 Mon Sep 17 00:00:00 2001 From: Sebastian Sebbie Silbermann Date: Thu, 20 Nov 2025 12:48:28 +0100 Subject: [PATCH 3/3] [react-dom] Fire `onReset` when automatically resetting forms --- packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js | 2 ++ packages/react-dom/src/__tests__/ReactDOMForm-test.js | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index e4c45ccc4c3f1..0a2ba645a2410 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -6466,5 +6466,7 @@ export const HostTransitionContext: ReactContext = { export type FormInstance = HTMLFormElement; export function resetFormInstance(form: FormInstance): void { + ReactBrowserEventEmitterSetEnabled(true); form.reset(); + ReactBrowserEventEmitterSetEnabled(false); } diff --git a/packages/react-dom/src/__tests__/ReactDOMForm-test.js b/packages/react-dom/src/__tests__/ReactDOMForm-test.js index 0c5972e6df198..789c97bbc8318 100644 --- a/packages/react-dom/src/__tests__/ReactDOMForm-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMForm-test.js @@ -1630,10 +1630,10 @@ describe('ReactDOMForm', () => { // We haven't reset yet. expect(inputRef.current.value).toEqual('3'); - // Action completes. onReset hasn't fired though. + // Action completes. onReset has been fired and values reset manually. await act(() => resolveText('Wait')); assertLog([]); - expect(inputRef.current.value).toEqual('3'); + expect(inputRef.current.value).toEqual('0'); }); it('requestFormReset schedules a form reset after transition completes', async () => {