-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Validating optional text inputs #310
Comments
I'm facing the same challenges regarding form validations and came up with the following solution. It can be further abstracted as needed. Any thoughts? import * as z from 'zod';
const optionalTextInput = (schema: z.ZodString) =>
z
.union([z.string(), z.undefined()])
.refine((val) => !val || schema.safeParse(val).success);
const emailInputSchema = optionalTextInput(z.string().email());
const emails = [undefined, "", "[email protected]", null, "foo", 12345];
const result = emails.map((email) => ({
email,
isValid: emailInputSchema.safeParse(email).success
}));
console.log(result);
// [
// {
// "email": undefined,
// "isValid": true
// },
// {
// "email": "",
// "isValid": true
// },
// {
// "email": "[email protected]",
// "isValid": true
// },
// {
// "email": null,
// "isValid": false
// },
// {
// "email": "foo",
// "isValid": false
// },
// {
// "email": 12345,
// "isValid": false
// }
// ] |
@glennreyes I understand the feeling that this is strangely convoluted. But the source of that convolution is the messy, chaotic behavior of web forms. Zod provides a rigorous, consistent way of defining types, so it can be complex to represent weird edge cases like those surfaced by poorly designed web standards 🤷♂️ I think implementing a new method or overloading Will some of the recent developments in TS surrounding tuples and rest parameters, it will shortly be possible to chain together calls to z.string().email().optional().or(z.literal('')) Which I think looks and feels a lot nicer than the But for now the approaches described by you and @jasonadkison are the right way to go. Because Zod is composable, you only have to define that schema once, then you can import it wherever you're defining forms. |
This was driving me crazy, thanks @colinhacks, appending |
If you want either the full string or import { z } from "zod"
const emptyStringToUndefined = z.literal("").transform(() => undefined)
export function asOptionalField<T extends z.ZodTypeAny>(schema: T) {
return schema.optional().or(emptyStringToUndefined)
} const form = createForm({
schema: z.object({
required: z.string().min(8),
optional: asOptionalField(z.string().min(8))
})
}) |
|
This thread is strangely tone deaf to an extremely common and obvious use-case, validating forms. Not sure how requiring copious boilerplate to make I appreciate, maybe, preserving the sanctity of bare |
@MinervaBot It's strange you feel entitled, like you're paying for zod |
Was their ever a resolution for this? For example
The input At the moment its not possible to define the class
:-) Not sure what to do here with Zod. |
I am also encountering this issue. I love zod for it's conciseness. It's confusing to encounter unexpected behaviour like this. |
+1 to this. Maybe there could be another method |
Is
|
For me I use the length minimal: |
This worked for me in setting a an optional URL |
Currently having the same problem with optional urls |
It looks the the |
|
@adaboese it is totally not perfect, the same strange magic moves with simple things, the same as |
How is this considered an edge-case? The current implementation of optional() clearly isn't working as many users expect, regardless of the underlying reasons with the web standards. At a bare minimum this should be highlighted on the docs if unwilling to amend or add an alternative optional() that covers this use case? |
Was also confused by this. It looks like the whole .optional() is obsolete in this case:
|
foo: z
.string()
.optional()
.transform(value => value || undefined)
.refine(
value => {
if (!value) return true
// remaining validation
},
{ message: 'lorem ipsum' }
) |
None of the solutions provided above seem to allow providing custom error messages.
I ended creating a "emptyOr" helper function that wraps the whole of it, such as:
|
@elie-delorme The method with |
I love you |
I think Colin mentioned on Twitter that Zod is supposed to be extensible via the prototype, we can extend the declare module 'zod' {
interface ZodString {
empty(): ZodUnion<[this, ZodLiteral<''>]>;
}
}
z.ZodString.prototype.empty = function () {
return this.or(z.literal(''));
};
// works
z.string().email().empty(); However, I didn't get it to work on a preceding |
thx a lot ❤️❤️❤️ |
Is this use case solved for in a less hacky way in the upcoming V4 release? This issue should not be closed IMO |
Any update please? The solution |
I ran the project only to get a zod error that my `EMAILER_` fields need to contain at least 1 character. Weirdly enough the `EMAILER_` env variables are optional. This comes back to `zod`'s way of handling empty strings, based on this comment from the creator of `zod` [here](colinhacks/zod#310 (comment)) This PR implements the suggestion in the comment This should prevent developer confusion when they have `EMAILER_` env variables represented as empty strings not working when running locally, which should accelerate their onboarding process.
This was quite the thread to find. I was also running into this issue, but most of the solutions suggested using unions (via In any case, I ended up finding a solution on Reddit that I tweaked a bit for my particular usage. Here it is if anyone else lands here with a similar use-case: // I've stored this in an easily importable library location in SvelteKit
// Something like `src/lib/schemas/schema-utils.ts`
import { z } from 'zod';
export function emptyAsNull<T extends z.ZodTypeAny>(schema: T) {
return z.preprocess((val) => {
if (typeof val === 'string' && val === '') {
return null;
} else {
return val;
}
}, schema);
} // Somewhere else in your app...
import { z } from 'zod';
import { emptyAsNull } from '$lib/schemas/schema-utils';
export const exampleSchema = z.object({
displayName: z.string().min(2), // Required, must be at least 2 characters
bio: emptyAsNull(z.string().min(10).nullish()) // Empty field in form data parses as `null`, otherwise a string that requires at least 2 characters
}); |
Considering an optional email text input: from user perspective I think the appropriate validation for this would be
z.string().email().optional()
which allows eitherundefined
or a valid email.In
React
, these types of inputs are almost never undefined. In fact, optional fields are considered empty strings.What is the common way to achieve validating these type of fields correctly with zod? I have tried things like
z.union([z.string().email, z.literal('')])
orrefine
, which felt a little unintuitive for a simple, yet very common optional field value.I was hoping for either a configurable
optional
type that could allow empty strings as well or a dedicated empty type for a string.Anyway, thank you for this great validation library, happy to help in any way!
The text was updated successfully, but these errors were encountered: