From c9cec8363bbbece760e58be63a8a397523f6ccec Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Mon, 23 Jun 2025 09:44:53 -0700 Subject: [PATCH] Add sumbitAction helper --- packages/nano-remix/src/clientHelpers.tsx | 44 ++++++++++++++++++++--- packages/nano-remix/src/main.ts | 7 ++-- packages/nano-remix/src/nanoRemix.ts | 2 -- packages/nano-remix/src/renderServer.tsx | 4 +-- 4 files changed, 43 insertions(+), 14 deletions(-) diff --git a/packages/nano-remix/src/clientHelpers.tsx b/packages/nano-remix/src/clientHelpers.tsx index 3e3cccf..e03a461 100644 --- a/packages/nano-remix/src/clientHelpers.tsx +++ b/packages/nano-remix/src/clientHelpers.tsx @@ -46,6 +46,43 @@ export const useAction = () => { return { data, loading: status === "submitting", error } } +// Helper to submit an action without a form +type SubmitActionOptions = { + url?: string + method?: "POST" | "PUT" | "PATCH" | "DELETE" +} +export const submitAction = async (data: any, options: SubmitActionOptions = {}) => { + const actionFns = window._actionFns + + try { + const { url = window.location.href, method = "POST" } = options + + actionFns?.setStatus("submitting") + + const body = new FormData() + Object.entries(data).forEach(([key, value]) => { + body.append(key, String(value)) + }) + + const res = await fetch(url, { method, body }) + + if (res.ok) { + const { actionData, loaderData } = (await res.json()) as any + window._setLoaderData!(loaderData) + + actionFns?.setData(actionData) + } else { + const errorText = await res.text() + throw new Error(`Error ${res.status}: ${errorText}`) + } + } catch (error) { + actionFns?.setError(`${error}`) + console.log(`🚨 Failed to submit`, error) + } finally { + actionFns?.setStatus("idle") + } +} + // Form component with built-in enhancement type FormProps = JSX.IntrinsicElements["form"] & { name: string } export const Form = (props: FormProps) => { @@ -62,11 +99,8 @@ export const Form = (props: FormProps) => { const form = e.currentTarget as HTMLFormElement const body = new FormData(form) body.append("_actionName", props.name) - const res = await fetch(form.action || window.location.href, { - method: props.method || "POST", - body, - headers: { Accept: "application/json" }, - }) + const url = form.action || window.location.href + const res = await fetch(url, { method: props.method || "POST", body }) if (res.status === 303) { window.location.href = res.headers.get("Location")! diff --git a/packages/nano-remix/src/main.ts b/packages/nano-remix/src/main.ts index ed931f8..a1bfe9f 100644 --- a/packages/nano-remix/src/main.ts +++ b/packages/nano-remix/src/main.ts @@ -1,8 +1,5 @@ -import { nanoRemix } from "@/nanoRemix" -import { Form, useAction, wrapComponentWithLoader } from "@/clientHelpers" - -export { Form, useAction, wrapComponentWithLoader } -export { nanoRemix } +export { Form, useAction, wrapComponentWithLoader, submitAction } from "@/clientHelpers" +export { nanoRemix } from "@/nanoRemix" export type Loader = ( req: Request, diff --git a/packages/nano-remix/src/nanoRemix.ts b/packages/nano-remix/src/nanoRemix.ts index 207ee1e..7c1bed1 100644 --- a/packages/nano-remix/src/nanoRemix.ts +++ b/packages/nano-remix/src/nanoRemix.ts @@ -21,8 +21,6 @@ export const nanoRemix = async (req: Request, options: Options = {}) => { const basename = ext ? url.pathname.slice(0, -ext.length) : url.pathname const route = router.match(basename) - console.log(`🌭 requesting ${url} got ${route?.pathname}`) - if (!route) { return new Response("Route Not Found", { status: 404, diff --git a/packages/nano-remix/src/renderServer.tsx b/packages/nano-remix/src/renderServer.tsx index 4fe0632..76a3f8e 100644 --- a/packages/nano-remix/src/renderServer.tsx +++ b/packages/nano-remix/src/renderServer.tsx @@ -1,8 +1,8 @@ import type { Action, Loader } from "@/main" export const renderServer = async (req: Request, route: Bun.MatchedRoute) => { - const accept = req.headers.get("Accept") + const contentType = req.headers.get("Content-Type") - if (accept === "application/json") { + if (contentType?.startsWith("multipart/form-data;")) { return await handleAction(req, route) } else { return await renderHtml(req, route)