Replies: 3 comments 6 replies
-
You can make the async function action({ requset }) {
const formData = await request.formData();
// The name can be anything. `operation` is just an example
const operation = formData.get('operation');
// ... parse formData as usual
}
function Example() {
return (
<form>
{/* ... */}
<button name="operation" value="add">Add</button>
<button name="operation" value="edit">Edit</button>
<button name="operation" value="delete">Delete</button>
</form>
);
} |
Beta Was this translation helpful? Give feedback.
-
Thinking about this again. It might be best using a discriminated union here as it would be possible to narrow down the type with a simple switch case. const schema = z.discriminatedUnion('type', [
z.object({
operation: z.literal('add'),
// ... add fields
}),
z.object({
operation: z.literal('edit'),
// ... edit fields
}),
z.object({
operation: z.literal('delete'),
// ... delete fields
}),
]),
async function action({ requset }) {
const formData = await request.formData();
const submission = parse(formData, { schema });
if (submission.intent !== 'submit' || !submission.value) {
return json(submission, { status: 400 });
}
switch (submission.value.operation) {
case 'add':
// ... add logic
break;
case 'edit':
// ...edit logic
break;
case 'delete':
// ... delete logic
break;
}
}
function Example() {
return (
<form>
{/* ... */}
<button name="operation" value="add">Add</button>
<button name="operation" value="edit">Edit</button>
<button name="operation" value="delete">Delete</button>
</form>
);
} |
Beta Was this translation helpful? Give feedback.
-
@edmundhung Great work on this library — really enjoying it so far! I'd love to see a guide for handling multiple forms with a single action, as it's a common pattern in Remix / React Router v7 and it's a little unclear how best to approach it. This is what I'm currently doing: import { Form, useActionData, type ActionFunctionArgs } from "react-router";
import z from "zod";
import { getZodConstraint, parseWithZod } from "@conform-to/zod/v4";
import { getFormProps, getInputProps, useForm } from "@conform-to/react";
enum Operation {
AddTodo = "addTodo",
EditTodo = "editTodo",
DeleteTodo = "deleteTodo",
}
const AddTodoFormData = z.object({
description: z.string().min(1),
});
const EditTodoFormData = z.object({
id: z.string().min(1),
description: z.string().min(1),
});
const DeleteTodoFormData = z.object({
id: z.string().min(1),
});
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
switch (formData.get("operation")) {
case Operation.AddTodo: {
const submission = parseWithZod(formData, { schema: AddTodoFormData });
if (submission.status !== "success") {
return submission.reply();
}
// ... add logic
return submission.reply({
formErrors: ["ADD not implemented"],
});
}
case Operation.EditTodo: {
const submission = parseWithZod(formData, { schema: EditTodoFormData });
if (submission.status !== "success") {
return submission.reply();
}
// ...edit logic
return submission.reply({
formErrors: ["EDIT not implemented"],
});
}
case Operation.DeleteTodo: {
const submission = parseWithZod(formData, { schema: DeleteTodoFormData });
if (submission.status !== "success") {
return submission.reply();
}
// ...delete logic
return submission.reply({
formErrors: ["DELETE not implemented"],
});
}
default:
throw new Error(`Unknown operation ${formData.get("operation")}`);
}
}
export default function Example() {
const actionData = useActionData<typeof action>();
const [addTodoForm, addTodoFields] = useForm({
lastResult:
actionData?.initialValue?.operation === Operation.AddTodo
? actionData
: undefined,
constraint: getZodConstraint(AddTodoFormData),
onValidate: ({ formData }) => {
return parseWithZod(formData, { schema: AddTodoFormData });
},
shouldValidate: "onBlur",
shouldRevalidate: "onInput",
});
const [editTodoForm, editTodoFields] = useForm({
lastResult:
actionData?.initialValue?.operation === Operation.EditTodo
? actionData
: undefined,
constraint: getZodConstraint(EditTodoFormData),
onValidate: ({ formData }) => {
return parseWithZod(formData, { schema: EditTodoFormData });
},
shouldValidate: "onBlur",
shouldRevalidate: "onInput",
});
const [deleteTodoForm] = useForm({
lastResult:
actionData?.initialValue?.operation === Operation.DeleteTodo
? actionData
: undefined,
constraint: getZodConstraint(DeleteTodoFormData),
onValidate: ({ formData }) => {
return parseWithZod(formData, { schema: DeleteTodoFormData });
},
shouldValidate: "onBlur",
shouldRevalidate: "onInput",
});
return (
<>
<Form method="post" {...getFormProps(addTodoForm)}>
<pre>{JSON.stringify(addTodoForm.errors, null, 2)}</pre>
<input type="hidden" name="operation" value={Operation.AddTodo} />
<input
{...getInputProps(addTodoFields.description, { type: "text" })}
placeholder="Description"
/>
<div>{addTodoFields.description.errors}</div>
<button type="submit">Add Todo</button>
</Form>
<br />
<br />
<Form method="post" {...getFormProps(editTodoForm)}>
<pre>{JSON.stringify(editTodoForm.errors, null, 2)}</pre>
<input type="hidden" name="operation" value={Operation.EditTodo} />
<input type="hidden" name="id" value="todo_123" />
<input
{...getInputProps(editTodoFields.description, { type: "text" })}
placeholder="Description"
/>
<div>{editTodoFields.description.errors}</div>
<button type="submit">Edit Todo</button>
</Form>
<br />
<br />
<Form method="post" {...getFormProps(deleteTodoForm)}>
<pre>{JSON.stringify(deleteTodoForm.errors, null, 2)}</pre>
<input type="hidden" name="operation" value={Operation.DeleteTodo} />
<input type="hidden" name="id" value="todo_123" />
<button type="submit">Delete Todo</button>
</Form>
</>
);
} So each form is treated separately from conforms perspective — some downsides include:
However, I think it get's us most the way — any thoughts? |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Hi, on the same route, I'm managing to add, update and delete, my schema validates if the name, type, currency, and id are not empty on add and edit, but on delete, i don't need that values, i need only the id to delete, how i manage different schemas?
schema:
Beta Was this translation helpful? Give feedback.
All reactions