Z
Zod•12mo ago
janglad

janglad - Okay another question 😅 Why does t...

Okay another question 😅 Why does this return a ZodEffect with a possible unnkown?
export const defAString = z.preprocess(
(val) => "this is def a string",
z.string(),
);
// const defAString: z.ZodEffects<z.ZodString, string, unknown>
export const defAString = z.preprocess(
(val) => "this is def a string",
z.string(),
);
// const defAString: z.ZodEffects<z.ZodString, string, unknown>
18 Replies
Scott Trinh
Scott Trinh•12mo ago
I don't remember the signature there but I think that is the input type, which makes sense for preprocess
janglad
jangladOP•12mo ago
ooh huh yes you're right
janglad
jangladOP•12mo ago
this does indeed work as expected
export const stringOrEmptyToNull = z.preprocess(
(val) => (val && typeof val === "string" ? val : null),
z.string().nullable()
)
// as z.ZodEffects<z.ZodNullable<z.ZodString>, string | null>;
// TODO: check above

const test = stringOrEmptyToNull.parse("test");
// const test: string | null
export const stringOrEmptyToNull = z.preprocess(
(val) => (val && typeof val === "string" ? val : null),
z.string().nullable()
)
// as z.ZodEffects<z.ZodNullable<z.ZodString>, string | null>;
// TODO: check above

const test = stringOrEmptyToNull.parse("test");
// const test: string | null
altho somehow it does show up as unknown here, which is fixed by using that as that is commented right now
No description
Scott Trinh
Scott Trinh•12mo ago
Yeah the output type is string so the type of the parsed value is string Every schema has an input and output type
janglad
jangladOP•12mo ago
oohhh yeah I am indeed using z.input hmm this is somewhat annoying lol cause IIRC I do need to do that as there are also transforms but then I'd need the output of that preprocess specifically 😄 altho actually no using z.output it still shows up as unknown there the schema setup is a bit complex so I'll need to do some digging as to where that's happening
Scott Trinh
Scott Trinh•12mo ago
What's the schema for street here?
janglad
jangladOP•12mo ago
that stringOrEmptyToNull but yes in the end it is indeed caused by using z.input which makes sense, output was just giving a different error which also makes sense but I guess I can just lie to TS here and use the as
Scott Trinh
Scott Trinh•12mo ago
Are you trying to construct a value and using the type as a type annotation without parsing it?
janglad
jangladOP•12mo ago
It's being used like this
const emptyForm = generateEmptyForm(user);

const defaultValues = create
? emptyForm
:
transFormListing(props.defaultValues, user);

const formSchema = create
? createListingInputSchema
: updateListingInputSchema;

type FormType = z.input<typeof formSchema>;

const form = useForm<FormType>({
defaultValues,
resolver: zodResolver(formSchema),
reValidateMode: "onSubmit",
});
const emptyForm = generateEmptyForm(user);

const defaultValues = create
? emptyForm
:
transFormListing(props.defaultValues, user);

const formSchema = create
? createListingInputSchema
: updateListingInputSchema;

type FormType = z.input<typeof formSchema>;

const form = useForm<FormType>({
defaultValues,
resolver: zodResolver(formSchema),
reValidateMode: "onSubmit",
});
Scott Trinh
Scott Trinh•12mo ago
Ahhh. Right. I wonder if instead of preprocess you should have a transform that checks for the empty string so that the input and output type is string | null still 🤔
janglad
jangladOP•12mo ago
that's not really ideal either. RHF sends an empty input as an empty string, if I then want to add on things like a min length I wouldn't be able to do say z.string().min(5).nullable().transform(emptyToNull) as when you pass it an empty string it complains about not being 5 long and while I can check for that in a refine, those only get triggered after all the other fields are valid so that's not great UX eitherr mind you I can't chain the min and such now either with the predefined variable but it is possible to do in a preprocess
Scott Trinh
Scott Trinh•12mo ago
z.union([
z.literal("").transform(() => null),
z.string().min(5).nullable(),
]);
z.union([
z.literal("").transform(() => null),
z.string().min(5).nullable(),
]);
You could do something like that maybe? Or:
z.string().transform((s) => s || null).pipe(
z.string().min(5).nullable()
);
z.string().transform((s) => s || null).pipe(
z.string().min(5).nullable()
);
janglad
jangladOP•12mo ago
second one would still have the issue of only validating after all the rest right but I had not thought of the first! that might actually be the most elegant solutioN!
Scott Trinh
Scott Trinh•12mo ago
I'm not sure what you mean here?
janglad
jangladOP•12mo ago
transforms and refines only run once other fields are valid right
Scott Trinh
Scott Trinh•12mo ago
It will be run in this case as long as it's a string maybe you're thinking about refines/transforms on an object (which requires that all of the properties pass their validations)?
janglad
jangladOP•12mo ago
yes I am indeed 😄
Scott Trinh
Scott Trinh•12mo ago
fwiw, I find the pipe implementation to be a bit more "accurate" to what you're trying to achieve in mapping an empty string to null and then adding addtional string constraints on any remaining strings, but both seem pretty readable. fwiw, i feel like these days pipe and coerce pretty much replace most use cases for preprocess.
Want results from more Discord servers?
Add your server