remix-run/react-router · error
Cannot submit element that is not
Error message
Cannot submit element that is not <form>, <button>, or <input type="submit|image">
What it means
React Router's submit path (useSubmit, useFetcher().submit, router.submit) extracts method/action/formData from a DOM target, but only <form>, <button>, and <input type="submit|image"> carry submission semantics. getFormSubmissionInfo() in lib/dom/dom.ts:339 throws this error when it receives any other HTMLElement. The library refuses to guess an action or method for elements that have none.
Solutions
- Submit the owning form instead of the control: submit(event.currentTarget.form)
- Or submit data directly instead of an element: submit({ q: value }, { method: 'get' })
- If using a button, make sure it is a real <button type="submit"> inside the <Form>
- Double-check that the ref/value you pass resolves to the <form> node itself
Example fix
// before
<select onChange={(e) => submit(e.currentTarget)} />
// after
<select onChange={(e) => submit(e.currentTarget.form)} /> Defensive patterns
Strategy: type-guard
Validate before calling
const el = e.currentTarget;
if (el instanceof HTMLFormElement || el instanceof HTMLButtonElement) {
submit(el);
} else if (el instanceof HTMLInputElement && (el.type === 'submit' || el.type === 'image')) {
submit(el);
} else if (el instanceof HTMLElement && el.form) {
submit(el.form);
} else {
submit(new FormData(el.form ?? undefined));
} Type guard
function isSubmittable(el: EventTarget | null): el is HTMLFormElement | HTMLButtonElement | HTMLInputElement {
if (!(el instanceof HTMLElement)) return false;
return (
el instanceof HTMLFormElement ||
el instanceof HTMLButtonElement ||
(el instanceof HTMLInputElement && (el.type === 'submit' || el.type === 'image'))
);
} Try / catch
try { submit(target) } catch (e) { if (e instanceof Error && e.message.startsWith('Cannot submit element')) { submit(target.form) } else { throw e } } Prevention
- Always submit the <form> node (e.currentTarget.form) from control change handlers
- Prefer data-based submit({ key: value }, { method: 'post' }) when you don't need DOM semantics
- Type refs as HTMLFormElement when they will be submitted
When it happens
Trigger: Calling submit(e.currentTarget) from a <select onChange={...}> (the currentTarget is the <select>, not the form); passing an <a>, <div>, or <input type="text"> to submit(); calling router.submit(document.querySelector('#wrapper')) where the node is a non-submittable element; submitting a ref that points to a container div instead of the <Form>.
Common situations: Filter/search dropdowns that auto-submit a form on change; migrating native form code where developers submit the changed control instead of the form; refs attached to wrapper elements around the form.
Related errors
- Cannot submit a or <input type="submit"> without a
- Cannot handle form actions without a decodeAction function
AI-assisted analysis of remix-run/react-router@6beaca3952 (2026-08-18).
Data as JSON: /api/errors/bbf5177fd1b19b55.
Report an issue: GitHub.
Appendix: source
Thrown at packages/react-router/lib/dom/dom.ts:340
// Build a FormData object populated from a form and submitter
formData = new FormData(form, target);
// If this browser doesn't support the `FormData(el, submitter)` format,
// then tack on the submitter value at the end. This is a lightweight
// solution that is not 100% spec compliant. For complete support in older
// browsers, consider using the `formdata-submitter-polyfill` package
if (!isFormDataSubmitterSupported()) {
let { name, type, value } = target;
if (type === "image") {
let prefix = name ? `${name}.` : "";
formData.append(`${prefix}x`, "0");
formData.append(`${prefix}y`, "0");
} else if (name) {
formData.append(name, value);
}
}
} else if (isHtmlElement(target)) {
throw new Error(
`Cannot submit element that is not <form>, <button>, or ` +
`<input type="submit|image">`,
);
} else {
method = defaultMethod;
action = null;
encType = defaultEncType;
body = target;
}
// Send body for <Form encType="text/plain" so we encode it into text
if (formData && encType === "text/plain") {
body = formData;
formData = undefined;
}
return { action, method: method.toLowerCase(), encType, formData, body };
}View on GitHub (pinned to 6beaca3952)