From c4b0701ec7334dd1db8fc9f499f006759d47b8df Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Tue, 29 Apr 2025 15:01:20 -0700 Subject: [PATCH] Fix multiple form submissions from throwing --- .../src/__tests__/ReactDOMForm-test.js | 42 +++++++++++++++++++ .../react-reconciler/src/ReactFiberHooks.js | 2 +- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMForm-test.js b/packages/react-dom/src/__tests__/ReactDOMForm-test.js index 93edccf9bc..f4a9355b10 100644 --- a/packages/react-dom/src/__tests__/ReactDOMForm-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMForm-test.js @@ -1670,6 +1670,48 @@ describe('ReactDOMForm', () => { expect(divRef.current.textContent).toEqual('Current username: acdlite'); }); + it.only('multiple form submissions in rapid succession do not throw', async () => { + const submitFormTwiceButtonRef = React.createRef(); + let actionCounter = 0; + function App() { + const formRef = React.createRef(); + // Submits the form twice in quick succession + // This simulates a user clicking a button twice in rapid succession + async function submitFormTwice() { + formRef.current.requestSubmit(); + await new Promise(res => setTimeout(res, 1)); + formRef.current.requestSubmit(); + } + + // This is a userspace action. it must take a non-zero amount of time to + // allow the form to be submitted again before the first one finishes. + // Otherwise, the form transitions will be batched and will not run concurrently. + async function submitForm() { + actionCounter++; + return new Promise(res => setTimeout(res, 1)); + } + + return ( + <> +
+ +
+ + + ); + } + + const root = ReactDOMClient.createRoot(container); + await act(() => root.render()); + + await act(async () => { + submitFormTwiceButtonRef.current.click(); + }); + expect(actionCounter).toBe(2); + }); + it( 'requestFormReset works with inputs that are not descendants ' + 'of the form element', diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index 91ed949ca0..cf150fde01 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -3261,7 +3261,7 @@ export function startHostTransition( function ensureFormComponentIsStateful(formFiber: Fiber) { const existingStateHook: Hook | null = formFiber.memoizedState; - if (existingStateHook !== null) { + if (existingStateHook !== null && existingStateHook.next !== null) { // This fiber was already upgraded to be stateful. return existingStateHook; }