react-router server action w/ D1 context and cancel button

Hello! So I have react-router application in framework mode with a simple form which uses a Bootstrap buttongroup:
<div className="btn-group" role="group">
<button type="submit" className="btn btn-outline-primary">Submit</button>
<button
className="btn btn-outline-warning"
onClick={(e) => { e.preventDefault(); navigate("/stores") }}
>Cancel</button>
</div>
<div className="btn-group" role="group">
<button type="submit" className="btn btn-outline-primary">Submit</button>
<button
className="btn btn-outline-warning"
onClick={(e) => { e.preventDefault(); navigate("/stores") }}
>Cancel</button>
</div>
The D1 database is available via the context in the action:
export async function action({ context }: Route.LoaderArgs) {
// do something with context.cloudflare.env.db
return redirect("/store/new");
}
export async function action({ context }: Route.LoaderArgs) {
// do something with context.cloudflare.env.db
return redirect("/store/new");
}
Everything works great for the "submit" path, but I can't discern the "cancel" from the "submit" in the action. The only way I can get it to work is to setup an inline handler on onClick() as above, as useNavigate() is not available in a handler function elsewhere in the file. This works fine, but it seems ... untidy. Any suggestions on how to determine which button was pressed in the action function? Or is the way I have it coded more or less the way to do it?
4 Replies
omortis
omortisOP2mo ago
Actually, this doesn't work at all, as I can't get the form data in the action. Now I'm confused again. Yeah. There are 2 mutually exclusive server side action signatures available: export async function action({ request }: Route.ActionArgs) or export async function action({ context }: Route.LoaderArgs) I'm not really sure what use the second one is. Sure, it's nice to have direct access to cloudflare assets via env, but you can't get the form data w/o the request?
olafg
olafg2mo ago
Route.LoaderArgs is for the route loader, i.e. the GET to a route in React Router Route.ActionArgs is for the action == POST You would need to get the FormData from the request, validate and sanitize, then you can store it in your D1 DB via the binding Here is a snippet from an app I have made:
export const loader = async ({ params, context }: Route.LoaderArgs) => ();

export const action = async ({
request,
params,
context,
}: Route.ActionArgs) => {
const formData = await request.formData();


return redirect("..");
};

export default function JSXComponent({ loaderData }: Route.ComponentProps) {
return ();
}
export const loader = async ({ params, context }: Route.LoaderArgs) => ();

export const action = async ({
request,
params,
context,
}: Route.ActionArgs) => {
const formData = await request.formData();


return redirect("..");
};

export default function JSXComponent({ loaderData }: Route.ComponentProps) {
return ();
}
This library is quite nice if you want to work with forms in React Router: https://conform.guide/
omortis
omortisOP2mo ago
Aaaaaaaa I was setting up my args to the action() function incorrectly. Awesome. Thanks @olafg . I will get back to this this evening (day job time) and update, here. Thanks, again. Cool. Works like a charm. Thanks again, @Olaf | CDN/Developer Platform ! Also thanks for the link to conform - I poked at zod a bit a few months ago but never followed up. I will check it out.
olafg
olafg2mo ago
Anytime! With conform you can still use Zod for validation, and gives some nice utilites to work with forms. It is actually made by someone who works at Cloudflare!

Did you find this page helpful?