remix-run/react-router · error

Cannot submit a or <input type="submit"> without a

Error message

Cannot submit a <button> or <input type="submit"> without a <form>

What it means

The DOM interceptor in @react-router/lib/dom/dom.ts handles submit events for router-aware forms. When the submit target is a <button> or an <input type="submit|image">, it reads target.form to inherit action/method/enctype from the associated form; if the control has no form (not nested in one and no valid form="id" attribute), target.form is null and it throws immediately — you cannot build a submission without a form.

Solutions

  1. Wrap the control in a <form> — preferably react-router's <Form> or <form method="post">
  2. Point the control at an existing form via <button form="form-id">
  3. If the button isn't meant to submit anything, give it type="button"

Example fix

// before
<button type="submit">Go</button> // no <form> ancestor

// after
import { Form } from "react-router";
<Form method="post">
  <button type="submit">Go</button>
</Form>
Defensive patterns

Strategy: type-guard

Validate before calling

// guard custom submit handling before reacting
document.addEventListener("submit", (e) => {
  const t = e.target;
  const associated =
    t instanceof HTMLFormElement ||
    ((t instanceof HTMLButtonElement || t instanceof HTMLInputElement) && t.form != null);
  if (!associated) return; // ignore stray submit events
});

Type guard

function isFormAssociatedSubmit(
  target: EventTarget | null,
): target is HTMLButtonElement | HTMLInputElement {
  return (
    (target instanceof HTMLButtonElement || target instanceof HTMLInputElement) &&
    target.form != null
  );
}

Prevention

When it happens

Trigger: A click/submit on <button type="submit"> that is not inside a <form>; a control whose form attribute references a non-existent or detached form id; the wrapping form being removed from the DOM before the event fires.

Common situations: Component refactor moves a button out of its form; the form="some-id" attribute has a typo'd or stale id; conditional rendering removes the <form> while keeping the button; portaling the button outside the form subtree.

Related errors


AI-assisted analysis of remix-run/react-router@6beaca3952 (2026-08-18). Data as JSON: /api/errors/2d3ac51ade5bd0f5. Report an issue: GitHub.

Appendix: source

Thrown at packages/react-router/lib/dom/dom.ts:300

  if (isFormElement(target)) {
    // When grabbing the action from the element, it will have had the basename
    // prefixed to ensure non-JS scenarios work, so strip it since we'll
    // re-prefix in the router
    let attr = target.getAttribute("action");
    action = attr ? stripBasename(attr, basename) : null;
    method = target.getAttribute("method") || defaultMethod;
    encType = getFormEncType(target.getAttribute("enctype")) || defaultEncType;

    formData = new FormData(target);
  } else if (
    isButtonElement(target) ||
    (isInputElement(target) &&
      (target.type === "submit" || target.type === "image"))
  ) {
    let form = target.form;

    if (form == null) {
      throw new Error(
        `Cannot submit a <button> or <input type="submit"> without a <form>`,
      );
    }

    // <button>/<input type="submit"> may override attributes of <form>

    // When grabbing the action from the element, it will have had the basename
    // prefixed to ensure non-JS scenarios work, so strip it since we'll
    // re-prefix in the router
    let attr = target.getAttribute("formaction") || form.getAttribute("action");
    action = attr ? stripBasename(attr, basename) : null;

    method =
      target.getAttribute("formmethod") ||
      form.getAttribute("method") ||
      defaultMethod;
    encType =
      getFormEncType(target.getAttribute("formenctype")) ||

View on GitHub (pinned to 6beaca3952)