remix-validated-form: [Bug]: `zfd.checkbox()` should require user to check by default
zfd.checkbox()
should mean the field is required (e.g accept terms and conditions) to follow zod’s pattern
Because your Union transforms undefined into false, technically this is a valid value:
export const checkbox = ({ trueValue = "on" }: CheckboxOpts = {}) =>
z.union([
z.literal(trueValue).transform(() => true),
z.literal(undefined).transform(() => false),
]);
I don’t believe the union is necesarry at all, as undefined
is the same as false
during checks.
If you skip that transform we can zfd.checkbox().optional()
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Comments: 34 (29 by maintainers)
I don’t really agree with this. I want
zfd.checkbox()
to be analogous toz.boolean()
. If the checkbox is checked it should betrue
, and if the checkbox is unchecked then it should befalse
. And likez.boolean()
, we shouldn’t make either value fail the validation by default.For better or worse, an unchecked checkbox is omitted from the
FormData
when the form is submitted. To me this means there’s no meaningful way to useoptional
since there’s no way for us to tell the difference between “unchecked” and “there is no checkbox”.That said, you can enforce that a checkbox is checked with
refine
.In the interest of not spiraling on this forever I think this is going to have to be my last comment on this issue. I’ve outlined in this comment what I see as the path forward for this.
I think this library should take form data and return properly typed and well modeled representation of the data in the form. From a data modeling perspective,
boolean
is a more accurate representation of a checkbox thantrue | undefined
.true | undefined
is also a “boolean”, it’s just not aboolean
which makes it confusing.If we don’t transform the value to a boolean, then why have a helper at all? It sounds like your ideal version of
checkbox
undefined
by defaultBut that’s the exact behavior of
z.string
.The purpose of having a helper for checkboxes at all, for me, is to get that coercion to happen automatically.
I’m also not super concerned about
optional
not doing anything oncheckbox
. zod surfaces theoptional
method even on schemas where it doesn’t make sense to have it (e.g. it’s valid to doz.string().optional().optional()
and the second optional does nothing).The state of a checkbox is also a boolean (checked vs not checked), even if it’s not encoded as a boolean type. And it can be checked even if the user hasn’t interacted with it (can default to checked).
What I’m ultimately trying to get at with
zfd.checkbox
is “is the checkbox checked, true or false?”. It’s up to the developer to make the judgement on a per-form basis if the user is required to check that checkbox or not. For the vast majority of forms I’ve written, both checked an unchecked were totally valid. Every once in awhile you have a form with a checkbox likeacceptsLicenseAgreement
where unchecked is invalid in that particular case.