[react-dom] Fire onReset when automatically resetting forms (#35176)
Sebastian "Sebbie" Silbermann committed
Feb 2, 2026 at 21:17 UTC
dcab44d757dee6fa8d40db56715732e891a3b729
4 files changed
+167
fixtures/dom/src/components/Header.js
+1
@@ -88,6 +88,7 @@ class Header extends React.Component {
88
<option value="/mouse-events">Mouse Events</option>
89
<option value="/selection-events">Selection Events</option>
90
<option value="/suspense">Suspense</option>
91
+ <option value="/form-actions">Form Actions</option>
92
<option value="/form-state">Form State</option>
93
<option value="/fragment-refs">Fragment Refs</option>
94
</select>
fixtures/dom/src/components/fixtures/form-actions/index.js
new
+113
@@ -0,0 +1,113 @@
1
+const React = window.React;
2
+
3
+const {useState} = React;
4
+
5
+async function defer(timeoutMS) {
6
+ return new Promise(resolve => {
7
+ setTimeout(resolve, timeoutMS);
8
+ });
9
+}
10
+
11
+export default function FormActions() {
12
+ const [textValue, setTextValue] = useState('0');
13
+ const [radioValue, setRadioValue] = useState('two');
14
+ const [checkboxValue, setCheckboxValue] = useState([false, true, true]);
15
+ const [selectValue, setSelectValue] = useState('three');
16
+
17
+ return (
18
+ <form
19
+ action={async () => {
20
+ await defer(500);
21
+ }}
22
+ onReset={() => {
23
+ setTextValue('0');
24
+ setRadioValue('two');
25
+ setCheckboxValue([false, true, true]);
26
+ setSelectValue('three');
27
+ }}>
28
+ <div style={{display: 'flex'}}>
29
+ <fieldset style={{flexBasis: 0}}>
30
+ <legend>type="text"</legend>
31
+ <input
32
+ type="text"
33
+ name="text"
34
+ value={textValue}
35
+ onChange={event => setTextValue(event.currentTarget.value)}
36
+ />
37
+ </fieldset>
38
+ <fieldset style={{flexBasis: 0}}>
39
+ <legend>type="radio"</legend>
40
+ <input
41
+ type="radio"
42
+ name="radio"
43
+ value="one"
44
+ checked={radioValue === 'one'}
45
+ onChange={() => setRadioValue('one')}
46
+ />
47
+ <input
48
+ type="radio"
49
+ name="radio"
50
+ value="two"
51
+ checked={radioValue === 'two'}
52
+ onChange={() => setRadioValue('two')}
53
+ />
54
+ <input
55
+ type="radio"
56
+ name="radio"
57
+ value="three"
58
+ checked={radioValue === 'three'}
59
+ onChange={() => setRadioValue('three')}
60
+ />
61
+ </fieldset>
62
+ <fieldset style={{flexBasis: 0}}>
63
+ <legend>type="checkbox"</legend>
64
+ <input
65
+ type="checkbox"
66
+ name="checkbox"
67
+ value="one"
68
+ checked={checkboxValue[0]}
69
+ onChange={event => {
70
+ const checked = event.currentTarget.checked;
71
+ setCheckboxValue(pending => [checked, pending[1], pending[2]]);
72
+ }}
73
+ />
74
+ <input
75
+ type="checkbox"
76
+ name="checkbox"
77
+ value="two"
78
+ checked={checkboxValue[1]}
79
+ onChange={event => {
80
+ const checked = event.currentTarget.checked;
81
+ setCheckboxValue(pending => [pending[0], checked, pending[2]]);
82
+ }}
83
+ />
84
+ <input
85
+ type="checkbox"
86
+ name="checkbox"
87
+ value="three"
88
+ checked={checkboxValue[2]}
89
+ onChange={event => {
90
+ const checked = event.currentTarget.checked;
91
+ setCheckboxValue(pending => [pending[0], pending[1], checked]);
92
+ }}
93
+ />
94
+ </fieldset>
95
+ <fieldset style={{flexBasis: 0}}>
96
+ <legend>select</legend>
97
+ <select
98
+ name="select"
99
+ value={selectValue}
100
+ onChange={event => setSelectValue(event.currentTarget.value)}>
101
+ <option value="one">one</option>
102
+ <option value="two">two</option>
103
+ <option value="three">three</option>
104
+ </select>
105
+ </fieldset>
106
+ </div>
107
+ <div>
108
+ <input type="reset" />
109
+ <input type="submit" />
110
+ </div>
111
+ </form>
112
+ );
113
+}
packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
+2
@@ -6562,5 +6562,7 @@ export const HostTransitionContext: ReactContext<TransitionStatus> = {
6562
6563
export type FormInstance = HTMLFormElement;
6564
export function resetFormInstance(form: FormInstance): void {
6565
+ ReactBrowserEventEmitterSetEnabled(true);
6566
form.reset();
6567
+ ReactBrowserEventEmitterSetEnabled(false);
6568
}
packages/react-dom/src/__tests__/ReactDOMForm-test.js
+51
@@ -1596,6 +1596,57 @@ describe('ReactDOMForm', () => {
1596
expect(divRef.current.textContent).toEqual('Current username: acdlite');
1597
});
1598
1599
+ it('should fire onReset on automatic form reset', async () => {
1600
+ const formRef = React.createRef();
1601
+ const inputRef = React.createRef();
1602
+
1603
+ let setValue;
1604
+ const defaultValue = 0;
1605
+ function App({promiseForUsername}) {
1606
+ const [value, _setValue] = useState(defaultValue);
1607
+ setValue = _setValue;
1608
+
1609
+ return (
1610
+ <form
1611
+ ref={formRef}
1612
+ action={async formData => {
1613
+ Scheduler.log(`Async action started`);
1614
+ await getText('Wait');
1615
+ }}
1616
+ onReset={() => {
1617
+ setValue(defaultValue);
1618
+ }}>
1619
+ <input
1620
+ ref={inputRef}
1621
+ text="text"
1622
+ name="amount"
1623
+ value={value}
1624
+ onChange={event => setValue(event.currentTarget.value)}
1625
+ />
1626
+ </form>
1627
+ );
1628
+ }
1629
+
1630
+ const root = ReactDOMClient.createRoot(container);
1631
+ await act(() => root.render(<App />));
1632
+
1633
+ // Dirty the controlled input
1634
+ await act(() => setValue('3'));
1635
+ expect(inputRef.current.value).toEqual('3');
1636
+
1637
+ // Submit the form. This will trigger an async action.
1638
+ await submit(formRef.current);
1639
+ assertLog(['Async action started']);
1640
+
1641
+ // We haven't reset yet.
1642
+ expect(inputRef.current.value).toEqual('3');
1643
+
1644
+ // Action completes. onReset has been fired and values reset manually.
1645
+ await act(() => resolveText('Wait'));
1646
+ assertLog([]);
1647
+ expect(inputRef.current.value).toEqual('0');
1648
+ });
1649
+
1650
it('requestFormReset schedules a form reset after transition completes', async () => {
1651
// This is the same as the previous test, except the form is updated with
1652
// a userspace action instead of a built-in form action.