Next.js Discord

Discord Forum

Help needed with form validation Types and re-usability in Server Actions

Answered
Bedlington Terrier posted this in #help-forum
Open in Discord
Avatar
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!
Image
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 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 data

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
View full answer

5 Replies

Avatar
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 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 data

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
Answer
Avatar
Bedlington TerrierOP
wow thank you! I will check this out but it looks like a cleaner solution than mine thank you
Avatar
Let me know if this works out for you
Avatar
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?
Avatar
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!