diff --git a/.changeset/wicked-avocados-fly.md b/.changeset/wicked-avocados-fly.md new file mode 100644 index 00000000000..41b9e5a1b58 --- /dev/null +++ b/.changeset/wicked-avocados-fly.md @@ -0,0 +1,5 @@ +--- +"@remix-run/react": patch +--- + +Throw a semantically correct 405 `ErrorResponse` instead of just an `Error` when submitting to a route without an `action` diff --git a/integration/action-test.ts b/integration/action-test.ts index aeb54ad30e7..b3861f3a153 100644 --- a/integration/action-test.ts +++ b/integration/action-test.ts @@ -95,6 +95,18 @@ test.describe("actions", () => { return
${PAGE_TEXT}
} `, + + "app/routes/no-action.tsx": js` + import { Form } from "@remix-run/react"; + + export default function Component() { + return ( +
+ +
+ ); + } + `, }, }); @@ -145,6 +157,22 @@ test.describe("actions", () => { await page.waitForSelector(`#text:has-text("${SUBMITTED_VALUE}")`); }); + test("throws a 405 when no action exists", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto(`/no-action`); + await page.click("button[type=submit]"); + await page.waitForSelector(`h1:has-text("405 Method Not Allowed")`); + expect(logs.length).toBe(2); + expect(logs[0]).toMatch('Route "routes/no-action" does not have an action'); + // logs[1] is the raw ErrorResponse instance from the boundary but playwright + // seems to just log the name of the constructor, which in the minified code + // is meaningless so we don't bother asserting + + // The rest of the tests in this suite assert no logs, so clear this out to + // avoid failures in afterEach + logs = []; + }); + test("properly encodes form data for request.text() usage", async ({ page, }) => { diff --git a/integration/resource-routes-test.ts b/integration/resource-routes-test.ts index 26ca80a7f5b..11639ffaaed 100644 --- a/integration/resource-routes-test.ts +++ b/integration/resource-routes-test.ts @@ -247,7 +247,7 @@ test.describe("loader in an app", async () => { await app.goto("/"); await app.clickSubmitButton("/no-action"); let html = await app.getHtml(); - expect(html).toMatch("Application Error"); + expect(html).toMatch("405 Method Not Allowed"); expect(logs[0]).toContain( 'Route "routes/no-action" does not have an action' ); diff --git a/packages/remix-react/routes.tsx b/packages/remix-react/routes.tsx index 87a40d4659c..84363794e1a 100644 --- a/packages/remix-react/routes.tsx +++ b/packages/remix-react/routes.tsx @@ -1,4 +1,5 @@ import * as React from "react"; +import { UNSAFE_ErrorResponseImpl as ErrorResponse } from "@remix-run/router"; import type { DataRouteObject, ShouldRevalidateFunction, @@ -140,7 +141,9 @@ export function createClientRoutes( `Route "${route.id}" does not have an action, but you are trying ` + `to submit to it. To fix this, please add an \`action\` function to the route`; console.error(msg); - return Promise.reject(new Error(msg)); + return Promise.reject( + new ErrorResponse(405, "Method Not Allowed", new Error(msg), true) + ); } return fetchServerHandler(request, route);