diff --git a/fixtures/dom/src/components/Header.js b/fixtures/dom/src/components/Header.js
index ae24b3223f64..1474d8053365 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 000000000000..eace38ba46be
--- /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 (
+
+ );
+}
diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
index 09653552aafe..a03ccc161ad1 100644
--- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
+++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
@@ -6562,5 +6562,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-bindings/src/events/SyntheticEvent.js b/packages/react-dom-bindings/src/events/SyntheticEvent.js
index acd50cf8e0c9..11ec03091941 100644
--- a/packages/react-dom-bindings/src/events/SyntheticEvent.js
+++ b/packages/react-dom-bindings/src/events/SyntheticEvent.js
@@ -522,6 +522,17 @@ export const SyntheticPointerEvent: $FlowFixMe = createSyntheticEvent(
PointerEventInterface,
);
+/**
+ * @interface SubmitEvent
+ * @see https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#the-submitevent-interface
+ */
+const SubmitEventInterface: EventInterfaceType = {
+ ...EventInterface,
+ submitter: 0,
+};
+export const SyntheticSubmitEvent: $FlowFixMe =
+ createSyntheticEvent(SubmitEventInterface);
+
/**
* @interface TouchEvent
* @see http://www.w3.org/TR/touch-events/
diff --git a/packages/react-dom-bindings/src/events/plugins/SimpleEventPlugin.js b/packages/react-dom-bindings/src/events/plugins/SimpleEventPlugin.js
index 8c983b3f5dfc..4873dacdbf43 100644
--- a/packages/react-dom-bindings/src/events/plugins/SimpleEventPlugin.js
+++ b/packages/react-dom-bindings/src/events/plugins/SimpleEventPlugin.js
@@ -27,6 +27,7 @@ import {
SyntheticWheelEvent,
SyntheticClipboardEvent,
SyntheticPointerEvent,
+ SyntheticSubmitEvent,
SyntheticToggleEvent,
} from '../../events/SyntheticEvent';
@@ -162,6 +163,9 @@ function extractEvents(
case 'pointerup':
SyntheticEventCtor = SyntheticPointerEvent;
break;
+ case 'submit':
+ SyntheticEventCtor = SyntheticSubmitEvent;
+ break;
case 'toggle':
case 'beforetoggle':
// MDN claims should not receive ToggleEvent contradicting the spec: https://html.spec.whatwg.org/multipage/indices.html#event-toggle
diff --git a/packages/react-dom/src/__tests__/ReactDOMEventPropagation-test.js b/packages/react-dom/src/__tests__/ReactDOMEventPropagation-test.js
index ebd3f9a54011..7e578fbba14e 100644
--- a/packages/react-dom/src/__tests__/ReactDOMEventPropagation-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMEventPropagation-test.js
@@ -672,9 +672,10 @@ describe('ReactDOMEventListener', () => {
reactEventType: 'submit',
nativeEvent: 'submit',
dispatch(node) {
- const e = new Event('submit', {
+ const e = new SubmitEvent('submit', {
bubbles: true,
cancelable: true,
+ submitter: null,
});
node.dispatchEvent(e);
},
diff --git a/packages/react-dom/src/__tests__/ReactDOMForm-test.js b/packages/react-dom/src/__tests__/ReactDOMForm-test.js
index 33988e9c4db1..f1a6cf791248 100644
--- a/packages/react-dom/src/__tests__/ReactDOMForm-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMForm-test.js
@@ -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 (
+
+ );
+ }
+
+ 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 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.
diff --git a/packages/react-dom/src/events/plugins/__tests__/SimpleEventPlugin-test.js b/packages/react-dom/src/events/plugins/__tests__/SimpleEventPlugin-test.js
index 062762e776a5..f14fcab168b9 100644
--- a/packages/react-dom/src/events/plugins/__tests__/SimpleEventPlugin-test.js
+++ b/packages/react-dom/src/events/plugins/__tests__/SimpleEventPlugin-test.js
@@ -591,4 +591,36 @@ describe('SimpleEventPlugin', function () {
);
});
});
+
+ it('includes the submitter in submit events', async function () {
+ container = document.createElement('div');
+
+ const onSubmit = jest.fn(event => {
+ event.preventDefault();
+ });
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render(
+ ,
+ );
+ });
+
+ const submitter = container.querySelector('#submitter');
+ const submitEvent = new SubmitEvent('submit', {
+ bubbles: true,
+ cancelable: true,
+ submitter: submitter,
+ });
+ await act(() => {
+ submitter.dispatchEvent(submitEvent);
+ });
+
+ expect(onSubmit).toHaveBeenCalledTimes(1);
+ const event = onSubmit.mock.calls[0][0];
+ expect(event.submitter).toBe(submitter);
+ });
});