Open verheyenkoen opened 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.
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?
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:
The validated action infers the zod/zfd schema type and Typescript yells when you pass it FormData.
Type FormData is missing the following properties from type '{ name: string; age: number; }': name, age
Inside the server action I get the FormData object and not the parsed data, even though Typescript infers this as the input type.
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.
- 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.
- 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) => { })
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);
});
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.
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
@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
.
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
andrevalidateTag
.I know you can do this:
but then we're spaghetticoding again, which the whole
action={myServerAction}
is trying to solve...