remix-run/react-router · error
Cannot handle form actions without a decodeAction function
Error message
Cannot handle form actions without a decodeAction function
What it means
Progressive-enhancement form posts (no JS) carry $ACTION_* keys in the FormData. Handling them requires React's decodeAction to rebuild the action from those fields; without it, processServerAction throws (lib/rsc/server.rsc.ts:723). decodeFormState is optional, decodeAction is not.
Solutions
- Pass decodeAction (and decodeFormState for useFormState support) from the RSC runtime into the server handler options
- Verify with a curl POST containing a $ACTION_ key that the server no longer throws
- If progressive enhancement is out of scope, ship forms that only submit via JS and document the limitation
Example fix
// before
const handler = createRequestHandler({ build, decodeReply, loadServerAction })
// after
const handler = createRequestHandler({
build,
decodeReply,
loadServerAction,
decodeAction,
decodeFormState,
}) Defensive patterns
Strategy: validation
Validate before calling
// Startup check for progressive enhancement support
if (typeof decodeAction !== 'function') {
console.warn('No-JS form submissions ($ACTION_*) will throw; pass decodeAction to the RSC handler');
} Prevention
- Pass decodeAction (and decodeFormState) alongside decodeReply when wiring custom RSC servers
- Include a curl form-POST smoke test with a $ACTION_ key in CI
- Keep handler wiring in one place so option sets stay in sync across upgrades
When it happens
Trigger: A form POST with $ACTION_* fields (JS disabled, curl test, bot, or a plain HTML POST) hitting an RSC handler created without decodeAction; smoke-testing forms via curl against a partially wired custom RSC server.
Common situations: Custom RSC server setups that wired only the fetch-action path (decodeReply) but not the form fallback; CI/curl tests that POST form encodings; crawlers replaying form submissions.
Related errors
- Cannot handle enhanced server action without decodeReply…
- Cannot submit a or <input type="submit"> without a
- Cannot submit element that is not
- Error splitting route module
- Failed to clone server response
AI-assisted analysis of remix-run/react-router@7aea711dd1 (2026-08-18).
Data as JSON: /api/errors/b3cfa253d85014b5.
Report an issue: GitHub.
Appendix: source
Thrown at packages/react-router/lib/rsc/server.rsc.ts:730
let formData =
maybeFormData &&
typeof maybeFormData === "object" &&
maybeFormData instanceof FormData
? maybeFormData
: null;
let skipRevalidation = formData?.has("$SKIP_REVALIDATION") ?? false;
return {
actionResult,
revalidationRequest: getRevalidationRequest(),
skipRevalidation,
};
} else if (isFormRequest) {
const formData = await request.clone().formData();
if (Array.from(formData.keys()).some((k) => k.startsWith("$ACTION_"))) {
if (!decodeAction) {
throw new Error(
"Cannot handle form actions without a decodeAction function",
);
}
const action = await decodeAction(formData);
let formState = undefined;
try {
let result = await action();
if (isRedirectResponse(result)) {
result = prependBasenameToRedirectResponse(result, basename);
}
formState = await decodeFormState?.(result, formData);
} catch (error) {
if (isRedirectResponse(error)) {
return prependBasenameToRedirectResponse(error, basename);
}
if (isResponse(error)) {
return error;
}View on GitHub (pinned to 7aea711dd1)