Help needed with form validation Types and re-usability in Server Actions
Answered
Bedlington Terrier posted this in #help-forum
Bedlington TerrierOP
Hi all,
I've decided to finally start and learn Typescript(going a bit slow) and need a bit of help. My question is about types and how I can use them to cater for my scenario that I believe it is not so unique.
See attached image: I have multiple forms where my users can edit specific detail about an article, in this example there's two forms. I specify the types for each form as distinct update types that I use to validate the input. Here comes my confusion:
I don't want to have multiple server actions with static types for the update of an Article. So how can I allow my Server action input to cater for these partial types and then save the validated data? The idea is to have more of these forms doing partial updates over time so I need a simple way to extend the capability.
Bonus question: Regarding, to data types for server actions - why are there some cases where people use FormData to pass in and other cases I see the inferred Form Types used? Is there a preference or recommended way to pass and handle the data?
Thanks in advance!
I've decided to finally start and learn Typescript(going a bit slow) and need a bit of help. My question is about types and how I can use them to cater for my scenario that I believe it is not so unique.
See attached image: I have multiple forms where my users can edit specific detail about an article, in this example there's two forms. I specify the types for each form as distinct update types that I use to validate the input. Here comes my confusion:
I don't want to have multiple server actions with static types for the update of an Article. So how can I allow my Server action input to cater for these partial types and then save the validated data? The idea is to have more of these forms doing partial updates over time so I need a simple way to extend the capability.
Bonus question: Regarding, to data types for server actions - why are there some cases where people use FormData to pass in and other cases I see the inferred Form Types used? Is there a preference or recommended way to pass and handle the data?
Thanks in advance!
Answered by jason
Thanks for illustrating your question clearly.
Here is a possible solution off the top of my head:
Within your zod schemas you can create a discriminated union that is based on your two existing schemas. Note the new field
Inside your server action you can do
Hopefully this works for you. Alternatively if this doesn't work for you, you could always pass an additional argument to your server action, similar to what I had for
Docs for passing an additional argument to your server action: https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#passing-additional-arguments
Here is a possible solution off the top of my head:
Within your zod schemas you can create a discriminated union that is based on your two existing schemas. Note the new field
type
// schemas.ts
export const updateArticleDetail = z.object({
name: z.string().min(4),
description: z.string().max(200),
published: z.boolean(),
type: z.literal("detail") // New
});
export const updateArticleContent = z.object({
body: z.string(),
preview: z.string(),
icon: z.string(),
color: z.string(),
type: z.literal("content") // New
});
export const updateArticleUnion = z.discriminatedUnion("type", [
updateArticleDetail,
updateArticleContent,
]);
export type UpdateArticleUnion = z.infer<typeof updateArticleUnion>;
Inside your server action you can do
export async function yourServerAction(_: any, formData: FormData) {
const result = updateArticleUnion.safeParse(formData);
if (!result.success) // handle your form errors
// If you really wanted to, you can determine what the shape of your formData is
// Whether it is in the shape of `updateArticleDetail` or `updateArticleContent`
switch (result.data.type) {
case "detail":
// Your form data is in the shape of `updateArticleDetail`
break;
case "content":
// Your form data is in the shape of `updateArticleContent`
break;
default:
}
}
Hopefully this works for you. Alternatively if this doesn't work for you, you could always pass an additional argument to your server action, similar to what I had for
type
, and then use the type
to match the corresponding schema to parse the form dataDocs for passing an additional argument to your server action: https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#passing-additional-arguments
5 Replies
Thanks for illustrating your question clearly.
Here is a possible solution off the top of my head:
Within your zod schemas you can create a discriminated union that is based on your two existing schemas. Note the new field
Inside your server action you can do
Hopefully this works for you. Alternatively if this doesn't work for you, you could always pass an additional argument to your server action, similar to what I had for
Docs for passing an additional argument to your server action: https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#passing-additional-arguments
Here is a possible solution off the top of my head:
Within your zod schemas you can create a discriminated union that is based on your two existing schemas. Note the new field
type
// schemas.ts
export const updateArticleDetail = z.object({
name: z.string().min(4),
description: z.string().max(200),
published: z.boolean(),
type: z.literal("detail") // New
});
export const updateArticleContent = z.object({
body: z.string(),
preview: z.string(),
icon: z.string(),
color: z.string(),
type: z.literal("content") // New
});
export const updateArticleUnion = z.discriminatedUnion("type", [
updateArticleDetail,
updateArticleContent,
]);
export type UpdateArticleUnion = z.infer<typeof updateArticleUnion>;
Inside your server action you can do
export async function yourServerAction(_: any, formData: FormData) {
const result = updateArticleUnion.safeParse(formData);
if (!result.success) // handle your form errors
// If you really wanted to, you can determine what the shape of your formData is
// Whether it is in the shape of `updateArticleDetail` or `updateArticleContent`
switch (result.data.type) {
case "detail":
// Your form data is in the shape of `updateArticleDetail`
break;
case "content":
// Your form data is in the shape of `updateArticleContent`
break;
default:
}
}
Hopefully this works for you. Alternatively if this doesn't work for you, you could always pass an additional argument to your server action, similar to what I had for
type
, and then use the type
to match the corresponding schema to parse the form dataDocs for passing an additional argument to your server action: https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#passing-additional-arguments
Answer
Bedlington TerrierOP
wow thank you! I will check this out but it looks like a cleaner solution than mine thank you
Let me know if this works out for you
Bedlington TerrierOP
I'm not sure what I'm doing wrong but on form submit, nothing is happening. Do I use the updateArticleUnion or one of the union schemas for resolver?
Bedlington TerrierOP
I see my validation failed because I was not including and handling the type field. Giving it a default value of 'content' (or any union value on related form) seems to do the trick. I would still need to handle the literal field that is not part of the data structure but that is fine. Thanks again for this solution!