diff --git a/.changeset/raw-action-dev-error.md b/.changeset/raw-action-dev-error.md new file mode 100644 index 00000000..8c07d2a5 --- /dev/null +++ b/.changeset/raw-action-dev-error.md @@ -0,0 +1,5 @@ +--- +"@solidjs/router": patch +--- + +Throw a descriptive error in development when an action is called directly instead of through `useAction`. diff --git a/src/data/action.ts b/src/data/action.ts index dc1c7c1b..567cd052 100644 --- a/src/data/action.ts +++ b/src/data/action.ts @@ -1,5 +1,5 @@ import { $TRACK, createMemo, createSignal, JSX, onCleanup, getOwner } from "solid-js"; -import { isServer } from "solid-js/web"; +import { isDev, isServer } from "solid-js/web"; import { useRouter } from "../routing.js"; import type { RouterContext, @@ -79,6 +79,20 @@ export function action, U = void>( options: string | { name?: string; onComplete?: (s: Submission) => void } = {} ): Action { function mutate(this: { r: RouterContext; f?: HTMLFormElement }, ...variables: T) { + if (isDev && (typeof this !== "object" || !this || !("r" in this))) { + throw new Error( + `An action was called directly. Wrap it with \`useAction\` first so it can track its submissions with the router: + + const myAction = action(async () => { ... }); + + function MyComponent() { + const submit = useAction(myAction); + return ; + } + +See https://docs.solidjs.com/solid-router/concepts/actions` + ); + } const router = this.r; const form = this.f; const p = ( diff --git a/test/data/action.spec.ts b/test/data/action.spec.ts index 200f12d6..56d2b09b 100644 --- a/test/data/action.spec.ts +++ b/test/data/action.spec.ts @@ -55,6 +55,12 @@ describe("action", () => { expect(testAction.url).toBe("https://action/test-action"); }); + test("should throw a descriptive error when called directly without `useAction`", () => { + const testAction = action(async () => "result", "direct-call-action"); + + expect(() => (testAction as any)()).toThrow(/useAction/); + }); + test("should create action with auto-generated hash when no `name` provided", () => { const testFn = async (data: string) => `result: ${data}`; const testAction = action(testFn);