T
TanStack2mo ago
xenial-black

Conditional zod schema on onSubmit form validator

Probably a skill issue but Is it possible to handle 2 zod schemas conditionally ? export const createEmployeeSchema = z.object({ email: z.string().email(), password: z.string().min(8), firstName: z.string().min(2), }); export const updateEmployeeSchema = createEmployeeSchema.omit({ password: true }); and on the form do something like this
validators: { onSubmit: employee ?updateEmployeeSchema : createEmployeeSchema,
},
validators: { onSubmit: employee ?updateEmployeeSchema : createEmployeeSchema,
},
Since not much changes besides the password field (which is removed in update form), I'd rather share the same form component But then on my password form.Field I get this typescript error when trying to render the errors for that field because the error object is unknown
No description
21 Replies
xenial-black
xenial-blackOP2mo ago
This is the form component
No description
xenial-black
xenial-blackOP2mo ago
I guess I can call it a type issue
exotic-emerald
exotic-emerald2mo ago
I think I'd use a https://zod.dev/api?id=discriminated-unions#discriminated-unions here and put the info if it is a "create" or and "update" into the Schema. You should be able to remove some conditions then as well (the once in validators.onSubmit, onSubmit at least)
employeeSchema =
z.discriminatedUnion("mode", [
z.object({
mode: z.literal("create"),
email: z.string().email(),
password: z.string().min(8),
firstName: z.string().min(2),
});
z.object({
mode: z.literal("update"),
email: z.string().email(),
password: z.any(), // does not matter when "create"
firstName: z.string().min(2),
});
])
employeeSchema =
z.discriminatedUnion("mode", [
z.object({
mode: z.literal("create"),
email: z.string().email(),
password: z.string().min(8),
firstName: z.string().min(2),
});
z.object({
mode: z.literal("update"),
email: z.string().email(),
password: z.any(), // does not matter when "create"
firstName: z.string().min(2),
});
])
To take it further you could use .transform() to transform the values the user inputs into the form to what your API needs.
const parsed = employeeSchema.parse(value);
updateMutation.mutate(parsed); // no need for type assertions
const parsed = employeeSchema.parse(value);
updateMutation.mutate(parsed); // no need for type assertions
xenial-black
xenial-blackOP2mo ago
Discriminated Union is a nice suggestion, I was able to get rid of the error on the form Field level, here's my current code for the useForm, I am wondering why doesn't the value inside if infer the type based on the values.type, am I missing something ?
No description
fair-rose
fair-rose2mo ago
because typescript assumes it‘s just some object instead of a zod schema input try extracting defaultValues and type it as z.input<>
xenial-black
xenial-blackOP2mo ago
Yeah, it takes the object as whole {} | {}
fair-rose
fair-rose2mo ago
and likely the type property as string instead of literal
xenial-black
xenial-blackOP2mo ago
Yeah typing default values with z.infer does the job
fair-rose
fair-rose2mo ago
input != infer == output works for this schema, but keep it in mind for schemas that have transforms
exotic-emerald
exotic-emerald2mo ago
defaultValues: {
type: !!employee ? 'update' : 'create',
firstname: employee?.firstName : '',
// …
password: '', // put all fields here even if they are not used in this "type" of form
}
defaultValues: {
type: !!employee ? 'update' : 'create',
firstname: employee?.firstName : '',
// …
password: '', // put all fields here even if they are not used in this "type" of form
}
defaultValues = z.input and I'd say it's good practive to v.parse before passing data to the mutation - this way if you change the Schema and you're adding transformations it "just works" 🪄
xenial-black
xenial-blackOP2mo ago
@ksgn is this good ?, I could probably just pass one object with conditional field like u just mentioned, but overall looks great
No description
exotic-emerald
exotic-emerald2mo ago
I think you can just upsertEmployeeSchema.parse in onSubmit - the Schema knows what to do depending on the type @Luca | LeCarbonator should the Schema on the right side not be exhaustive? (Lines 19-22) So: Instead of omit just relax the rules z.any or z.string
xenial-black
xenial-blackOP2mo ago
By relaxing you mean just duplicate whatever is on createEmployeeSchema and make the password/role z.any?
fair-rose
fair-rose2mo ago
when it comes to the output, it will remove any extra. If I understood it correctly, it won‘t error for having any extra unless you add .strict()
exotic-emerald
exotic-emerald2mo ago
that's what I do, but I'm using valibot. I'm also transforming my schemas to what the Backend wants and parsing (as I suggested before). That pattern works pretty nicely You'll end up with a Schema that you can throw data against, it'll take it and transform it to the expected output or it'll provide you with an error message
xenial-black
xenial-blackOP2mo ago
Okay that's interesting, I would like to see that approach, I made a codesandbox to play with it, https://codesandbox.io/p/devbox/gifted-tristan-yjy584 Im kinda confused though, im typing the defaultValues as upsertingEmployeeSchema which is a discriminatedUnion, Im also conditionally parsing the schema but when I hover over parsedValue inside value.type === 'create' it still shows both types,
fair-rose
fair-rose2mo ago
that‘s because upsert accepts both create and update, which means the parsed output may be both create and update so the condition check before passing it in should be unnecessary
exotic-emerald
exotic-emerald2mo ago
if (parsedValue.type === "create") {
console.log(parsedValue)
}
if (parsedValue.type === "create") {
console.log(parsedValue)
}
inside the if parsedValue is now:
email: string;
password: string;
firstName: string;
lastName: string;
role: "worker";
phoneNumber: string;
address: string;
type: "create";
profileImage?: string | undefined;
email: string;
password: string;
firstName: string;
lastName: string;
role: "worker";
phoneNumber: string;
address: string;
type: "create";
profileImage?: string | undefined;
use the parsedValue - once you've put the value you get from onSubmit through zods parse you probably do not want to use it anymore
xenial-black
xenial-blackOP2mo ago
Oh, I was checking against the value from submit, got it, thanks a lot guys for the feedback
xenial-black
xenial-blackOP2mo ago
@ksgn Ill stick to this version, LGTM
No description
xenial-black
xenial-blackOP2mo ago
It feels better to omit password and role from the schema IMO

Did you find this page helpful?