pingdotgg / zact

Nothing to see here
https://zact-example.vercel.app
MIT License
989 stars 16 forks source link

How about FormData? #25

Open verheyenkoen opened 1 year ago

verheyenkoen commented 1 year ago

I guess this doesn't work out-of-the-box with FormData (as zod doesn't really have a built-in way to work with FormData)?

Also, if it did and I wanted to create a regular form with a server action that posts the form data via Next.js, would there ever be a way to catch validation errors and display those cleanly within your form? Currently the docs suggest you can end that scenario only with redirect, revalidatePath and revalidateTag.

I know you can do this:

<form onSubmit={async (e) => {
   e.preventDefault();
   const myData = ...
   const response = await myServerAction(myData);
   // Deal with validation errors here
}}

but then we're spaghetticoding again, which the whole action={myServerAction} is trying to solve...

chungweileong94 commented 1 year ago

For form data, this is what you are looking for, https://www.npmjs.com/package/zod-form-data. I'm currently looking into building something like remix-validated-form, which is probably something we can refer to.

verheyenkoen commented 1 year ago

I know there's zod extension packages for FormData, but I guess you can't just go and use those with zact, or am I wrong there?

verheyenkoen commented 1 year ago

Made a quick POC now and turns out I can use zod-form-data out of the box for that but a few problems remain:

chungweileong94 commented 1 year ago
  • The validated action infers the zod/zfd schema type and Typescript yells when you pass it FormData.

Yeah, there's a problem with the input type, which I already created PR #19 to address that. As of now, you can patch the package locally to workaround.

chungweileong94 commented 1 year ago
  • Other problem (probably unrelated to FormData) is that the ZodValidationError thrown in case of invalid data loses its' issues bag of data to break down errors on a field level. But you do get the error message through to the client though.

Yeah, that's something I'm trying to figure out. The simplest solution I can think of is to have an option to prevent error throwing, like zact.(z.string(), { throwValidationError: false }), but I don't really like that DX personally.

Or even better, we can borrow something from trpc

zact
  .input(z.string(), { throwValidationError: false })
  .action((input) => { })
sam3d commented 10 months ago

Aren't server actions just functions? I'm unsure why Zod's primitives for this aren't sufficient:

"use server";

export const action = z
  .function()
  .args(z.object({ hello: z.boolean() }))
  .implement(async (data) => {
    console.log("Server", data);
  });
chungweileong94 commented 10 months ago

Aren't server actions just functions? I'm unsure why Zod's primitives for this aren't sufficient

You are actually correct, Zod alone is sufficient enough if you only want to validate the input data. But it wasn't enough if you want to catch the invalid input and display the error on to client-side.

verheyenkoen commented 10 months ago

I resorted to this setup with react-hook-form as an intermediary with Zod validation. Basically validation is first done on the client, and the data is sent as an object instead of FormData to the sever where you can reuse the same Zod schema for server-side validation. Whatever is still invalid at that point has been messed with by the end user so you can basically throw dirty errors that are handled by an error boundary (or Next.js error pages) from that point I guess...

Tutorial: https://www.youtube.com/watch?v=h3w9tQoXx5I&t=1s&pp=ygUSem9kIHNlcnZlciBhY3Rpb25z

chungweileong94 commented 10 months ago

@verheyenkoen Yeah, having the client-side validation works too, I personally use it myself. But when it comes to progressive enhancement, they are not really an option.

I personally don't really enjoy having a form library and validation library bundle in client code, as they add quite a bit to the bundle size, even for a simple form. I always wanted to do something similar to remix-validated-form, validation on server-side, but it just not possible with NextJS yet. I heard they (React/Vercel Team) are planning something in the near future, something similar to the experimental useOptimistic & useFormStatus.