Last active
February 15, 2023 12:32
-
-
Save itsMapleLeaf/705e2989f91d49912a3ffd9d0520ec68 to your computer and use it in GitHub Desktop.
SolidStart-style server actions in Remix
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function CreateTodoButton() { | |
const submit = CreateTodoAction.useSubmit() | |
const isSubmitting = CreateTodoAction.useSubmissions().length > 0 | |
const params = useParams() | |
return ( | |
<button | |
type="button" | |
title="Create Todo" | |
disabled={isSubmitting} | |
onClick={() => submit({ projectId: params.projectId!, text: "New Todo" })} | |
> | |
{isSubmitting ? <LoadingSpinner size={6} /> : <Plus />} | |
</button> | |
) | |
} | |
const CreateTodoAction = serverAction( | |
"create-todo", | |
async (input: { projectId: string; text: string }, request) => { | |
const user = await getSessionUser(request) | |
const todo = await db.todos.create({ | |
data: { ...input, creatorId: user.id }, | |
}) | |
// because this runs in the server, can't import this at the top level (for now?) | |
const { redirect } = await import("@remix-run/node") | |
return redirect(`/projects/${input.projectId}/todos/${todo.id}`) | |
}, | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { type ActionArgs } from "@remix-run/node" | |
import { handleServerAction } from "~/server-actions" | |
// set up the resource route to handle action submissions | |
export async function action({ request, params }: ActionArgs) { | |
return handleServerAction(request, params["actionName"]!) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { type TypedResponse } from "@remix-run/node" | |
import { | |
useFetcher, | |
useFetchers, | |
useSubmit, | |
useTransition, | |
} from "@remix-run/react" | |
import { type MaybePromise } from "./helpers/types" | |
declare global { | |
var __serverActionRegistry: | |
| Map< | |
string, | |
(input: any, request: Request) => MaybePromise<TypedResponse<any>> | |
> | |
| undefined | |
} | |
const registry = (globalThis.__serverActionRegistry ??= new Map< | |
string, | |
(input: any, request: Request) => MaybePromise<TypedResponse<any>> | |
>()) | |
// for my app-like needs, I'm relying more on submit calls than <Form> elements, | |
// which is better for type safety anyway, | |
// but an ideal implementation would include an alternate `formServerAction` | |
// for cases where that's easier | |
export function serverAction<Input, Return>( | |
actionName: string, | |
callback: ( | |
input: Input, | |
request: Request, | |
) => MaybePromise<TypedResponse<Return>>, | |
) { | |
registry.set(actionName, callback) | |
const actionUrl = `/server-actions/${actionName}` | |
return { | |
useSubmit: function useServerActionSubmit() { | |
const submit = useSubmit() | |
return (data: Input) => { | |
submit( | |
{ data: JSON.stringify(data) }, | |
{ method: "post", action: actionUrl }, | |
) | |
} | |
}, | |
useFetcher: function useServerActionFetcher() { | |
const fetcher = useFetcher<Return>() | |
return { | |
...fetcher, | |
Form: undefined, | |
submit: (data: Input) => { | |
fetcher.submit( | |
{ data: JSON.stringify(data) }, | |
{ method: "post", action: actionUrl }, | |
) | |
}, | |
} | |
}, | |
useSubmissions: function useServerActionSubmissions() { | |
const transition = useTransition() | |
const fetchers = useFetchers() | |
return [transition, ...fetchers].flatMap((state) => { | |
if (state.submission?.action !== actionUrl) return [] | |
return JSON.parse( | |
state.submission.formData.get("data") as string, | |
) as Input | |
}) | |
}, | |
} | |
} | |
export async function handleServerAction(request: Request, actionName: string) { | |
const action = registry.get(actionName) | |
if (!action) { | |
return new Response(undefined, { | |
status: 404, | |
statusText: `No action found for ${actionName}`, | |
}) | |
} | |
const formData = await request.formData() | |
const input = JSON.parse(formData.get("data") as string) | |
return await action(input, request) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment