facebook/react · error · Error

A React form was unexpectedly submitted. If you called form.

Error message

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().

What it means

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.

Source

Thrown at packages/react-dom-bindings/src/client/ReactDOMComponent.js:562

      domElement.setAttribute(key, sanitizedValue);
      break;
    }
    case 'action':
    case 'formAction': {
      // TODO: Consider moving these special cases to the form, input and button tags.
      if (__DEV__) {
        validateFormActionInDevelopment(tag, key, value, props);
      }
      if (typeof value === 'function') {
        // Set a javascript URL that doesn't do anything. We don't expect this to be invoked
        // because we'll preventDefault, but it can happen if a form is manually submitted or
        // if someone calls stopPropagation before React gets the event.
        // If CSP is used to block javascript: URLs that's fine too. It just won't show this
        // error message but the URL will be logged.
        domElement.setAttribute(
          key,
          // eslint-disable-next-line no-script-url
          "javascript:throw new Error('" +
            '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().' +
            "')",
        );
        break;
      } else if (typeof prevValue === 'function') {
        // When we're switching off a Server Action that was originally hydrated.
        // The server control these fields during SSR that are now trailing.
        // The regular diffing doesn't apply since we compare against the previous props.
        // Instead, we need to force them to be set to whatever they should be now.
        // This would be a lot cleaner if we did this whole fork in the per-tag approach.
        if (key === 'formAction') {
          if (tag !== 'input') {
            // Setting the name here isn't completely safe for inputs if this is switching
            // to become a radio button. In that case we let the tag based override take
            // control.

View on GitHub (pinned to eafeac097b)

Solutions

  1. Replace form.submit() with form.requestSubmit() — it dispatches a real submit event React can intercept and preventDefault
  2. Remove or reorder event.stopPropagation() in submit handlers, or call event.preventDefault() explicitly alongside it
  3. If you must submit before React handles it, dispatch submit only after hydration/commit completes
  4. For programmatic resets, use the requestSubmit of the form ref React gives you rather than native submit()

Example fix

// before
const formRef = useRef(null);
<button onClick={() => formRef.current.submit()}>Save</button>;

// after
const formRef = useRef(null);
<button onClick={() => formRef.current.requestSubmit()}>Save</button>;
Defensive patterns

Strategy: fallback

Validate before calling

function submitFormSafely(form: HTMLFormElement | null) {
  if (form == null) return;
  if (typeof form.requestSubmit === 'function') {
    form.requestSubmit(); // fires submit event React can preventDefault
  } else {
    // legacy fallback: dispatch manually so handlers still run
    form.dispatchEvent(new Event('submit', {bubbles: true, cancelable: true}));
  }
}

Type guard

const isSubmitEventUnclaimed = (e: Event) => !e.defaultPrevented;

Try / catch

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.

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of facebook/react@eafeac097b (2026-08-21). Data as JSON: /api/errors/3783018f69c8eaad. Report an issue: GitHub.