[react-dom] Fire onReset when automatically resetting forms (#35176)

This commit is contained in:
Sebastian "Sebbie" Silbermann
2026-02-02 21:17:14 +01:00
committed by GitHub
parent b8a6bfa22c
commit dcab44d757
4 changed files with 167 additions and 0 deletions

View File

@@ -88,6 +88,7 @@ class Header extends React.Component {
<option value="/mouse-events">Mouse Events</option>
<option value="/selection-events">Selection Events</option>
<option value="/suspense">Suspense</option>
<option value="/form-actions">Form Actions</option>
<option value="/form-state">Form State</option>
<option value="/fragment-refs">Fragment Refs</option>
</select>

View File

@@ -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 (
<form
action={async () => {
await defer(500);
}}
onReset={() => {
setTextValue('0');
setRadioValue('two');
setCheckboxValue([false, true, true]);
setSelectValue('three');
}}>
<div style={{display: 'flex'}}>
<fieldset style={{flexBasis: 0}}>
<legend>type="text"</legend>
<input
type="text"
name="text"
value={textValue}
onChange={event => setTextValue(event.currentTarget.value)}
/>
</fieldset>
<fieldset style={{flexBasis: 0}}>
<legend>type="radio"</legend>
<input
type="radio"
name="radio"
value="one"
checked={radioValue === 'one'}
onChange={() => setRadioValue('one')}
/>
<input
type="radio"
name="radio"
value="two"
checked={radioValue === 'two'}
onChange={() => setRadioValue('two')}
/>
<input
type="radio"
name="radio"
value="three"
checked={radioValue === 'three'}
onChange={() => setRadioValue('three')}
/>
</fieldset>
<fieldset style={{flexBasis: 0}}>
<legend>type="checkbox"</legend>
<input
type="checkbox"
name="checkbox"
value="one"
checked={checkboxValue[0]}
onChange={event => {
const checked = event.currentTarget.checked;
setCheckboxValue(pending => [checked, pending[1], pending[2]]);
}}
/>
<input
type="checkbox"
name="checkbox"
value="two"
checked={checkboxValue[1]}
onChange={event => {
const checked = event.currentTarget.checked;
setCheckboxValue(pending => [pending[0], checked, pending[2]]);
}}
/>
<input
type="checkbox"
name="checkbox"
value="three"
checked={checkboxValue[2]}
onChange={event => {
const checked = event.currentTarget.checked;
setCheckboxValue(pending => [pending[0], pending[1], checked]);
}}
/>
</fieldset>
<fieldset style={{flexBasis: 0}}>
<legend>select</legend>
<select
name="select"
value={selectValue}
onChange={event => setSelectValue(event.currentTarget.value)}>
<option value="one">one</option>
<option value="two">two</option>
<option value="three">three</option>
</select>
</fieldset>
</div>
<div>
<input type="reset" />
<input type="submit" />
</div>
</form>
);
}

View File

@@ -6562,5 +6562,7 @@ export const HostTransitionContext: ReactContext<TransitionStatus> = {
export type FormInstance = HTMLFormElement;
export function resetFormInstance(form: FormInstance): void {
ReactBrowserEventEmitterSetEnabled(true);
form.reset();
ReactBrowserEventEmitterSetEnabled(false);
}

View File

@@ -1596,6 +1596,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 (
<form
ref={formRef}
action={async formData => {
Scheduler.log(`Async action started`);
await getText('Wait');
}}
onReset={() => {
setValue(defaultValue);
}}>
<input
ref={inputRef}
text="text"
name="amount"
value={value}
onChange={event => setValue(event.currentTarget.value)}
/>
</form>
);
}
const root = ReactDOMClient.createRoot(container);
await act(() => root.render(<App />));
// 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 has been fired and values reset manually.
await act(() => resolveText('Wait'));
assertLog([]);
expect(inputRef.current.value).toEqual('0');
});
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.