T
TanStackβ€’5mo ago
harsh-harlequin

Best practice typing a boolean field using zod (or other schema library)

Let's say I have a field in my form where a user has to pick "yes" or "no". There is no default. Arguably I would use a zod type like so isDogOwner: z.boolean() Now, if I don't provide a default value in my defaultValues, typescript will complain that it's not matching the schema. If I make it optional in zod, it will be optional in defaultValues.. but now my form validation is not right since I need the user to make a choice before submitting. In text fields this is not a problem since you just use an empty string, but what do we do in these scenarios? I feel like something in the typescript implementation is wrong here, my gut is telling me that defaultValues should be a Partial of the schema so you can chose if you shall provide a default or not for a specific field. Thoughts, suggestions?
12 Replies
correct-apricot
correct-apricotβ€’5mo ago
tanstack form works with the input type of zod schemas. That means you can achieve the effect you want using transform, refine or superRefine z.boolean().nullable().refine(v => v !== null, 'Please select an option!') That will turn the output type into a boolean, but allow null as an input type. in onSubmit, you will have to parse value once more to receive the output value, as you'll receive the input type
harsh-harlequin
harsh-harlequinOPβ€’5mo ago
Thanks for the quick feedback! that make sense. No zod expert here, will try it out πŸ™‚
eastern-cyan
eastern-cyanβ€’5mo ago
⬆️ this Also, Valibot has a picklist (https://valibot.dev/api/picklist/) that might be another approach to this problem. isDogOwner: v.picklist(["yes", "no"]) Maybe this turns into something like this at some point… isDogOwner: v.picklist(["no", "1 dog", "2 dogs"]) or even hasPets: v.picklist(["no", "1 dog", "2 dogs", "1 cat", "2 cats"]) That's what I've seen for rental properties (you get the option to take 1 or two dogs/cats max)
harsh-harlequin
harsh-harlequinOPβ€’5mo ago
const schema = z.object({
isDogOwner: z.boolean().nullable().refine((v) => v !== null, "please select"),
})

const testSchema = useAppForm({
defaultValues: {
isDogOwner: false,
},
validators: {
onBlur: schema,
},
onSubmit: (form) => {
console.log(form.value);
}
})
const schema = z.object({
isDogOwner: z.boolean().nullable().refine((v) => v !== null, "please select"),
})

const testSchema = useAppForm({
defaultValues: {
isDogOwner: false,
},
validators: {
onBlur: schema,
},
onSubmit: (form) => {
console.log(form.value);
}
})
I tried this little snippet, but I still got some type errors. On the onBlur field I got The types of 'input.isDogOwner' are incompatible between these types. Type 'boolean | null' is not assignable to type 'null'. Type 'false' is not assignable to type 'null'.ts(2322) I'm on zod 3.24.3 and tanstack 1.6.3
correct-apricot
correct-apricotβ€’5mo ago
what's likely happening is that * it infers that isDogOwner is boolean since you passed false to it * sees that the schema is nullable try extracting the defaultValues so that it knows it's related to the schema:
const defaultValues: z.input<typeof schema> = {
isDogOwner: false
}

const testSchema = useAppForm({
defaultValues,
validators: {
onBlur: schema,
},
onSubmit: (form) => {
console.log(form.value);
}
})
const defaultValues: z.input<typeof schema> = {
isDogOwner: false
}

const testSchema = useAppForm({
defaultValues,
validators: {
onBlur: schema,
},
onSubmit: (form) => {
console.log(form.value);
}
})
harsh-harlequin
harsh-harlequinOPβ€’5mo ago
Yepp that did work πŸ™‚ Would be nice if you wouldn't have to be explicit about it though
correct-apricot
correct-apricotβ€’5mo ago
well, that's a limitation of typescript. it implicitly creates a separate type since you didn't hint at the schema and defaultValues being related at all
harsh-harlequin
harsh-harlequinOPβ€’5mo ago
Yeah, I guess it depends on if the type is defined by the defaultValues field or by the schema no? Now it's more that you need to pass in a schema that matches the type defined by your defaultValues field.
correct-apricot
correct-apricotβ€’5mo ago
typescript has functionality to control the flow of assignment (NoInfer, Infer), but this is a case of conflict between two infers so you could tell typescript to never use defaultValues as reference, but people tend to do telling it to not use schemas as reference would break the code you provided and the current situation has conflicts as you saw
harsh-harlequin
harsh-harlequinOPβ€’5mo ago
telling it to not use schemas as reference would break the code you provided
What do you mean by this? My code was already broken πŸ™‚ But If useAppForm would use input type from the zod schema It would work since it's a wider type. But if this would be better or not I'm not sure, but it's what i expected in this case
correct-apricot
correct-apricotβ€’5mo ago
yeah, that's what I meant. It wouldn't solve the situation. And if it worked in your favor, it would break every person that didn't pass schemas at least that's how I understood the current typing. I'm not too well versed in typescript to know if a solution would exist
harsh-harlequin
harsh-harlequinOPβ€’5mo ago
mmm same here.. I would assume that you could fallback to the type of defaultValues if a schema was not provided But thanks! I would consider my problem solved πŸ˜„

Did you find this page helpful?