Add sumbitAction helper

This commit is contained in:
Corey Johnson 2025-06-23 09:44:53 -07:00
parent eb4a1fac14
commit c9cec8363b
4 changed files with 43 additions and 14 deletions

View File

@ -46,6 +46,43 @@ export const useAction = <TAction extends Action>() => {
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")!

View File

@ -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<Data extends object> = (
req: Request,

View File

@ -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,

View File

@ -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)