This is copied from my GitHub discussion post:
I'm very new to both React and react-router, so apologies if I'm making an obvious mistake.
I'm working on a pretty basic CRUD app that stores posts, using Mantine UI components. The page to edit a post has a form with a submit button to save changes. It does this via a fetcher that calls my API endpoint /posts/<postId>/edit:
app/routes/posts.$postId_.edit.tsx
import type { Post } from "~/api";
import type { Route } from "./+types/posts.$postId_.edit";
import { ActionIcon, Stack, TextInput } from "@mantine/core";
import { useForm } from "@mantine/form";
import { useFetcher } from "react-router";
export async function clientAction({ request, params }: Route.ClientActionArgs) {
const data = await request.formData();
const id = params.postId;
const res = await fetch(`/api/posts/${id}/edit`, {
method: "POST",
body: data,
});
return res;
}
export default function ShowPost({ loaderData }: Route.ComponentProps) {
const post = loaderData;
const fetcher = useFetcher();
const form = useForm({
// --- form setup ---
});
return (
<fetcher.Form method="post">
<ActionIcon type="submit">Save</ActionIcon>
<Stack>
<TextInput
name="title"
key={form.key("title")}
{...form.getInputProps("title")}
/>
{/* --- further form inputs --- */}
</Stack>
</fetcher.Form>
);
}
This works fine. The issue is that I want to add a second button that calls the endpoint /posts/<postId>/delete to delete a post. I've tried to do this by adding a value to each button and using that to determine the action to take in clientAction:
app/routes/posts.$postId_.edit.tsx
export async function clientAction({ request, params }: Route.ClientActionArgs) {
// --- get submitted data ---
const intent = data.get("intent");
if (intent === "edit") {
return await fetch(`/api/posts/${id}/edit`, {
method: "POST",
body: data,
});
} else if (intent === "delete") {
await fetch(`/api/posts/${id}/delete`, {
method: "POST",
});
return redirect("/");
}
}
export default function ShowPost({ loaderData }: Route.ComponentProps) {
// --- same setup as before ---
const deleteModal = () => {
modals.open({
title: "Delete post",
children: (
<Stack>
<Text>Are you sure you want to delete this post?</Text>
<fetcher.Form method="post">
<ActionIcon
type="submit"
name="intent"
value="delete"
>
Yes, delete it
</ActionIcon>
</fetcher.Form>
</Stack>
),
});
};
return (
<fetcher.Form method="post">
<ActionIcon onClick={deleteModal}>Delete</ActionIcon>
<ActionIcon
type="submit"
name="intent"
value="edit"
>
Save
</ActionIcon>
<Stack>
<TextInput
name="title"
key={form.key("title")}
{...form.getInputProps("title")}
/>
{/* --- further form inputs --- */}
</Stack>
</fetcher.Form>
);
}
However, with this approach, the HTML <form> that gets rendered inside the modal has the attribute action="/", which of course sends the request to the root route instead of the current route and causes a 405 Method Not Allowed error. The <form> element with the original edit button, however, works normally, with no action attribute set.
Of course, I'm able to specifically set the modal form with the delete button to have action={`/posts/${post.id.toString()}/edit`}, and that works fine. But this feels a bit too hacky, and I'd like to understand why this is happening in the first place.
Is there a better way to do this?
I'm very new to both React and react-router, so apologies if I'm making an obvious mistake. I'm working on a pretty basic CRUD app that stores posts, using Mantine UI components. The pa...