Repository: facebook/react
Pull request: ReactDOM: Fix missing form data when the submitter is outside the form

## Summary

Fixes https://github.com/facebook/react/issues/27391

Fix form actions to include the `name` and `value` from the `form-associated element` when it is located outside the form.

## How did you test this change?

- Existing test has been modified to include the case from the issue.
- Manually tested running Next.js locally, linked to a local build of `react-dom`.
<img width="637" alt="Screenshot 2024-01-22 at 8 45 20 PM" src="https://github.com/facebook/react/assets/762112/6a63f7eb-e5ad-4acd-921e-9f4caf05fac6">

- Created codesandbox
   - [Sandbox with the bug](https://codesandbox.io/p/sandbox/kind-resonance-jmvgcr) (using the commit 6315c9b9, I just picked the previous commit by the time of creating the sandbox).
<img width="400" alt="Screenshot 2024-05-01 at 4 09 12 PM" src="https://github.com/facebook/react/assets/762112/f9148764-d0fb-43d4-ad59-42b5a520dfbf">

   - [Sandbox with the build from this PR](https://codesandbox.io/p/sandbox/modest-chihiro-8vp77x)
<img width="400" alt="Screenshot 2024-05-01 at 4 08 40 PM" src="https://github.com/facebook/react/assets/762112/5b180b17-360e-4de9-9315-8ed933dffff0">


Changed files:
filename: packages/react-dom-bindings/src/events/plugins/FormActionEventPlugin.js
patch: @@ -92,6 +92,9 @@ function extractEvents(
       const temp = submitter.ownerDocument.createElement('input');
       temp.name = submitter.name;
       temp.value = submitter.value;
+      if (form.id) {
+        temp.setAttribute('form', form.id);
+      }
       (submitter.parentNode: any).insertBefore(temp, submitter);
       formData = new FormData(form);
       (temp.parentNode: any).removeChild(temp);

filename: packages/react-dom/src/__tests__/ReactDOMForm-test.js
patch: @@ -481,6 +481,7 @@ describe('ReactDOMForm', () => {
   it('can read the clicked button in the formdata event', async () => {
     const inputRef = React.createRef();
     const buttonRef = React.createRef();
+    const outsideButtonRef = React.createRef();
     let button;
     let title;
 
@@ -492,14 +493,27 @@ describe('ReactDOMForm', () => {
     const root = ReactDOMClient.createRoot(container);
     await act(async () => {
       root.render(
-        <form action={action}>
-          <input type="text" name="title" defaultValue="hello" />
-          <input type="submit" name="button" value="save" />
-          <input type="submit" name="button" value="delete" ref={inputRef} />
-          <button name="button" value="edit" ref={buttonRef}>
-            Edit
+        <>
+          <form action={action}>
+            <input type="text" name="title" defaultValue="hello" />
+            <input type="submit" name="button" value="save" />
+            <input type="submit" name="button" value="delete" ref={inputRef} />
+            <button name="button" value="edit" ref={buttonRef}>
+              Edit
+            </button>
+          </form>
+          <form id="form" action={action}>
+            <input type="text" name="title" defaultValue="hello" />
+          </form>
+          <button
+            form="form"
+            name="button"
+            value="outside"
+            ref={outsideButtonRef}>
+            Button outside form
           </button>
-        </form>,
+          ,
+        </>,
       );
     });
 
@@ -520,6 +534,11 @@ describe('ReactDOMForm', () => {
     expect(button).toBe('edit');
     expect(title).toBe('hello');
 
+    await submit(outsideButtonRef.current);
+
+    expect(button).toBe('outside');
+    expect(title).toBe('hello');
+
     // Ensure that the type field got correctly restored
     expect(inputRef.current.getAttribute('type')).toBe('submit');
     expect(buttonRef.current.getAttribute('type')).toBe(null);