ITADN

`toTypedSchema` initializes nested object fields as `{field: undefined}` instead of `undefined` when root schema is `ZodObject`, but not when it is `ZodEffects`

#5156Openhowiezhao 创建于 2026-05-07
H
howiezhaocommented
When using `useForm` with `toTypedSchema`, a nested object field that is **not** present in `initialValues` gets initialized differently depending on whether the root schema is a plain `ZodObject` or a `ZodEffects` (produced by `.refine()` / `.superRefine()`). - **Root schema is `ZodObject` (no `.refine()`)** → field is initialized as `{ value: undefined, label: undefined }` - **Root schema is `ZodEffects` (with `.refine()`)** → field is initialized as `undefined` This inconsistency is surprising because the shape of the nested field itself has not changed — only the root schema wrapper differs. ## Minimal Reproduction ``` const schema = z.object({ name: z.string(), priceType: z.object({ value: z.string(), label: z.string(), }), }); // Without .refine() — root is ZodObject const { values } = useForm({ validationSchema: toTypedSchema(schema), initialValues: { name: '' }, }); console.log(values.priceType); // → { value: undefined, label: undefined } // With .refine() — root is ZodEffects const { values: values2 } = useForm({ validationSchema: toTypedSchema(schema.refine(() => true)), initialValues: { name: '' }, }); console.log(values2.priceType); // → undefined ``` ## Observed Cause The `cast` function calls `getDefaults(zodSchema)` when `zodSchema.parse()` throws. `getDefaults` checks `instanceof ZodObject` at the top level: ``` function getDefaults(schema) { if (!(schema instanceof ZodObject)) { return undefined; // ZodEffects reaches this and returns early } return Object.fromEntries(Object.entries(schema.shape).map(([key, value]) => { // ... if (value instanceof ZodObject) { return [key, getDefaults(value)]; // produces { value: undefined, label: undefined } } return [key, undefined]; })); } ``` When the root is `ZodEffects`, `getDefaults` returns `undefined` immediately, so no synthetic defaults are injected and the nested field stays `undefined`. When the root is `ZodObject`, `getDefaults` recurses into nested `ZodObject` fields and synthesizes an empty object for them. ## Workaround Explicitly set the field to `undefined` in `initialValues`: ``` const { values } = useForm({ validationSchema: toTypedSchema(schema), initialValues: { name: '', priceType: undefined, }, }); ``` ## Question Is it intended that nested object fields without an explicit `.default()` are synthesized as `{ field: undefined }` rather than left as `undefined`? And if so, should the behavior be consistent regardless of whether the root schema is `ZodObject` or `ZodEffects`?
0 条评论