-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
How can I allow null
as input value of a schema, but not as a valid output?
#1206
Comments
You can achieve this with a transform, but that means that you're going to have to "pick a random number or date to use as the default" in the case where you pass I don't have a lot of experience with |
@scotttrinh That makes sense, but does Like, the following seems work type-wise, but not sure I understand what exactly happens with the validation in this case: const zodSchema= z.number()
.positive()
.nullable()
.transform((value) => value ?? NaN)
type ZodValues = z.input<typeof zodSchema>;
// number | null
type ZodValid = z.output<typeof zodSchema>;
// number |
As for the |
It happens after input parsing, if that makes sense. The flow goes: flowchart LR
A[input] --> B{Nullable?};
B -- null --> D[Transform];
B -- number --> C{Positive?};
B -- other --> F[Throw];
C -- true --> D[Transform];
C -- false --> F[Throw];
D -- null --> G[NaN];
D -- number --> H[number];
I don't think you need to trigger an extra parse, I believe the integration already passes it through the parser. You might need to add some type annotations in our submit handler or something like that, but I think you can/should trust the output of |
Reading your flow diagram, it then seems that |
Yeah, just confirmed it. |
Yeah, I think maybe that's the crux of the issue: the two types describes valid inputs and valid outputs. It doesn't describe the domain of expected inputs that might be sent (that is For my purposes, I don't care that much about the type of the form state since forms have a very loose set of data structures compared to my much more restricted domain model. Whatever works is fine and I trust that the schema does the right thing in all of the situations such that I can "trust" the parsed output. Does that make sense? I don't know how that squares with the various form libraries, though. |
Yeah, and that's why I said you'll have to map |
Discovered the z
.positive()
.nullable()
.transform((value, ctx): number => {
if (value == null)
ctx.addIssue({
code: 'custom',
message: 'X Cannot be null',
});
return value ?? NaN;
}) That gets correct type, and stops it with a validation issue. Will be quite annoying to have to add that to every nullable number though, haha. Does |
I already commented in another issue, but it seems more appropriate here. I'm terribly confused how to deal with FWIW I'm encountering similar struggles when attempting to work with number inputs, using Given...
How do you represent a number input? |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
I'm feeling some problems with this issue too when dealing with databases. Values from databases are usually null. Ideally, I would parse them with zod and transform all nulls into undefined. Null has some weird behavior and therefore I would rather use undefined if I can. Does anyone know if it is possible to transform all nulls on outputs, but allow outputs on inputs? |
I'm facing this issue too when building forms. The value is correctly |
Hey @Svish! I used the
then
|
Setting the default values works with the const date = methods.watch('nested.date1') This always returns the form field type set by Zod ( |
@lukasvice Yep, I have the same issue. But, I think it needs to be fixed in |
I've been challenged with this same issue. Documented the question in Discussions before I saw this Issue: Code here: https://gist.github.com/TonyGravagno/2b744ceb99e415c4b53e8b35b309c29c 🚨 I'm hoping this ticket gets re-opened because it's not a closed topic. Ref #1953 where we're collaborating on code to generate a valid Default object from a Zod schema. Is this exactly what's done in React Hook Form? While I am using react-hook-form, in code that is not using that great library I would still like to use Zod, so I'd prefer not to have to rely on the external library to generate defaults for this one. At this moment I'm struggling with the simple concept where: I don't want the default. I'm intentionally setting the date as null or undefined because I want safeParse to fail until a valid value is set. |
@TonyGravagno RHF simply has a Re your date issue, that's an issue with your own code. If you don't want |
You can now use the const schema = z.object({
age: z
.number()
.positive()
.nullable()
.transform((value, ctx): number => {
if (value == null) {
ctx.addIssue({
code: "invalid_type",
expected: "number",
received: "null"
});
return z.NEVER;
}
return value;
})
});
type SchemaIn = z.input<typeof schema>;
type SchemaOut = z.output<typeof schema>;
const form = useForm<SchemaIn, never, SchemaOut>({
resolver: zodResolver(schema),
defaultValues: {
age: null
}
}); This also works with Check out https://codesandbox.io/s/rhf-zod-defaultvalue-vxmncm?file=/src/App.tsx However, it would be really great to have a better solution for the "transform" / "addIssue" thing. |
Update: I ended up doing something like this: const schema = z.object({
age: z.number().positive()
});
type SchemaOut = z.input<typeof schema>;
type SchemaIn = Omit<SchemaOut, 'age'> & {
age: SchemaOut[age] | null
} Or use a |
In case if someone still facing the issue. Zod has a dedicated paragraph on how to do type refinements, which in my case also works for const schema = z.object({
file: z
.instanceof(File)
.nullable()
.superRefine((arg, ctx): arg is File => {
if (!(arg instanceof File)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Select a file',
fatal: true, // abort early on this error in order not to fall down to the next refine with a null value
});
}
return z.NEVER;
})
.refine(
// here typescript already knows that the "file" is a File
// But if we don't do "fatal: true" in superRefinement, zod will also pass null, despite TS says it's a File
file => file.size < 10 * 1024 * 1024,
`Size limit exceeded`,
)
})
type FormInitial = z.input<typeof schema>; // { file: File | null }
type FormSubmit = z.output<typeof schema>; // { file: File }
// then use it with react-hook-form
const form = useForm<FormInitial, any, FormSubmit>({
resolver: zodResolver(schema),
defaultValues: {
file: null, // accepts File | null
},
});
form.handleSubmit(data => /** data here is a FormSubmit type **/)
const file = form.watch('file'); // "file" is "File | null" I saw the answer with using transform, but in my opinion the transform should be used for data transformation, e.g. changing it's type, shape or whatever. Refine is for validation purposes, which is the case when my schema accepts |
I have to correct myself. Since my solution works fine, I ended up using const nullableInput = <T extends ZodTypeAny>(
schema: T,
message = 'Output value can not be null',
) => {
return schema.nullable().transform((val, ctx) => {
if (val === null) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
fatal: true,
message,
});
return z.NEVER;
}
return val;
});
};
// usage:
const schema = z.object({
photo: nullableInput(z.instanceof(File)),
name: nullableInput(z.string().min(1)),
age: nullableInput(z.number().min(18), 'Please, provide age'), // with custom error message
});
export type FormInitial = z.input<typeof schema>; // { photo: File | null, name: string | null, age: number | null }
export type FormSubmit = z.output<typeof schema>; // { file: File, name: string, age: number } There is also a separate issue, which describes why |
I'm trying to use
zod
andreact-hook-form
together, and find it a bit difficult to deal settingdefaultValues
in a way that makes bothreact-hook-form
,typescript
and the actual schema validation happy.Say you have this schema:
Here both
ZodValues
andZodValid
does not allownull
as a value.If I add
nullable
, we get this:Using
yup@0.32.11
(latest now), it seems I'm able to do it like this, which is what I want:Is there any way I can write this schema with
zod
, so that the input allowsnull
, while the output does not?The issue is that
react-hook-form
preferably wants non-undefined default values for the input, and for e.g.number
andDate
inputs I'd really prefer to usenull
as I do not want to pick a random number or date to use as the default.The text was updated successfully, but these errors were encountered: