{"record":{"id":"3783018f69c8eaad","repo":"facebook/react","slug":"a-react-form-was-unexpectedly-submitted-if-you-ca","errorCode":null,"errorMessage":"A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you're trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().","messagePattern":"A React form was unexpectedly submitted\\. If you called form\\.submit\\(\\) manually, consider using form\\.requestSubmit\\(\\) instead\\. If you're trying to use event\\.stopPropagation\\(\\) in a submit event handler, consider also calling event\\.preventDefault\\(\\)\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/react-dom-bindings/src/client/ReactDOMComponent.js","lineNumber":562,"sourceCode":"      domElement.setAttribute(key, sanitizedValue);\n      break;\n    }\n    case 'action':\n    case 'formAction': {\n      // TODO: Consider moving these special cases to the form, input and button tags.\n      if (__DEV__) {\n        validateFormActionInDevelopment(tag, key, value, props);\n      }\n      if (typeof value === 'function') {\n        // Set a javascript URL that doesn't do anything. We don't expect this to be invoked\n        // because we'll preventDefault, but it can happen if a form is manually submitted or\n        // if someone calls stopPropagation before React gets the event.\n        // If CSP is used to block javascript: URLs that's fine too. It just won't show this\n        // error message but the URL will be logged.\n        domElement.setAttribute(\n          key,\n          // eslint-disable-next-line no-script-url\n          \"javascript:throw new Error('\" +\n            'A React form was unexpectedly submitted. If you called form.submit() manually, ' +\n            \"consider using form.requestSubmit() instead. If you\\\\'re trying to use \" +\n            'event.stopPropagation() in a submit event handler, consider also calling ' +\n            'event.preventDefault().' +\n            \"')\",\n        );\n        break;\n      } else if (typeof prevValue === 'function') {\n        // When we're switching off a Server Action that was originally hydrated.\n        // The server control these fields during SSR that are now trailing.\n        // The regular diffing doesn't apply since we compare against the previous props.\n        // Instead, we need to force them to be set to whatever they should be now.\n        // This would be a lot cleaner if we did this whole fork in the per-tag approach.\n        if (key === 'formAction') {\n          if (tag !== 'input') {\n            // Setting the name here isn't completely safe for inputs if this is switching\n            // to become a radio button. In that case we let the tag based override take\n            // control.","sourceCodeStart":544,"sourceCodeEnd":580,"githubUrl":"https://github.com/facebook/react/blob/eafeac097ba51e1eab809c07102126bd5f8e5425/packages/react-dom-bindings/src/client/ReactDOMComponent.js#L544-L580","documentation":"When a <form> has a function action (Server Action or form action prop), React cannot put a real URL in the action attribute, so it writes action=\"javascript:throw new Error('A React form was unexpectedly submitted...')\" as a placeholder. React normally preventDefaults the submit event so the URL never runs; the error only fires when the browser actually navigates to that javascript: URL because React's handler did not get the event.","triggerScenarios":"Calling form.submit() programmatically (bypasses the submit event entirely); calling event.stopPropagation() in your own submit handler before React's root listener runs; submitting a hydrated form before hydration has attached React's listener; CSP logging the blocked javascript: URL instead of showing the throw.","commonSituations":"Migrating imperative jQuery-style form code to Server Actions; nested libraries (analytics, validation) that stopPropagation on submit; slow hydration where a user submits before React is ready; forms inside third-party modals that call submit() directly.","solutions":["Replace form.submit() with form.requestSubmit() — it dispatches a real submit event React can intercept and preventDefault","Remove or reorder event.stopPropagation() in submit handlers, or call event.preventDefault() explicitly alongside it","If you must submit before React handles it, dispatch submit only after hydration/commit completes","For programmatic resets, use the requestSubmit of the form ref React gives you rather than native submit()"],"exampleFix":"// before\nconst formRef = useRef(null);\n<button onClick={() => formRef.current.submit()}>Save</button>;\n\n// after\nconst formRef = useRef(null);\n<button onClick={() => formRef.current.requestSubmit()}>Save</button>;","handlingStrategy":"fallback","validationCode":"function submitFormSafely(form: HTMLFormElement | null) {\n  if (form == null) return;\n  if (typeof form.requestSubmit === 'function') {\n    form.requestSubmit(); // fires submit event React can preventDefault\n  } else {\n    // legacy fallback: dispatch manually so handlers still run\n    form.dispatchEvent(new Event('submit', {bubbles: true, cancelable: true}));\n  }\n}","typeGuard":"const isSubmitEventUnclaimed = (e: Event) => !e.defaultPrevented;","tryCatchPattern":"Wrap the manual submit call: try { form.requestSubmit(); } catch (e) { console.error('Submit blocked', e); } — but note the throw happens in the javascript: URL context, not here, so prevention (requestSubmit) is the real fix.","preventionTips":["Ban form.submit() in code review; always requestSubmit()","Never stopPropagation() on submit without also preventDefault()","Keep submit buttons as type=\"submit\" so the native path goes through React's listener","Monitor hydration completion before enabling imperative submit buttons"],"tags":["react-dom","forms","server-actions","submit"],"backgroundTag":"form-unexpected-submission","analyzedSha":"eafeac097ba51e1eab809c07102126bd5f8e5425","analyzedAt":"2026-08-21T22:01:08.818Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}