Replies: 1 comment
|
The issue you are encountering with The Root CauseIn Zod, When your schema transforms
By default, Because The Solution: Explicit GenericsReact Hook Form v7 provides three type parameters on useForm<TFieldValues, TContext, TTransformedValues>()
Here is the complete setup: import { useForm, SubmitHandler } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const vector3DBase = z.object({
x: z.number().nullable(),
y: z.number().nullable(),
z: z.number().nullable(),
});
export const formSchema = z.object({
myPoint: vector3DBase
.refine((val) => val !== undefined, { message: 'Required' })
.transform((val) => {
const allNull = val.x === null && val.y === null && val.z === null;
return allNull ? null : val;
}),
});
export type FormInput = z.input<typeof formSchema>;
export type FormOutput = z.output<typeof formSchema>;
export function MyComponent() {
const { control, handleSubmit, register } = useForm<FormInput, unknown, FormOutput>({
resolver: zodResolver(formSchema),
defaultValues: {
myPoint: { x: null, y: null, z: null },
},
});
const onSubmit: SubmitHandler<FormOutput> = (data) => {
// data.myPoint is typed as Vector3D | null
console.log('Submitted payload:', data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input type="number" {...register('myPoint.x', { valueAsNumber: true })} />
<input type="number" {...register('myPoint.y', { valueAsNumber: true })} />
<input type="number" {...register('myPoint.z', { valueAsNumber: true })} />
<button type="submit">Submit</button>
</form>
);
}Key takeaways:
|
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
I have a form that has many vector3d components
const vector3DBase = z .object({ x: z.number().nullable(), y: z.number().nullable(), z: z.number().nullable(), })And example schema that uses it
export const formSchema = z.object({ myPoint: vector3DSchema(), });MyPoint can be null from the API. However for validation purposes (and I pass it as lens.focus("myPoint")) I don't want it to be nullable. When I send it to the server I need to transform it and if x,y,z is null then return null, otherwise return object.
So from API myPoint: vector3d | null
In the form {x:number|null, y: number| null, z: number|null}
Validate
either x:number, y:number, z:number OR
x:null, y:null, z:null
When posting to server
If x,y,z is null => whole object should be null
x,y,z is number => normal object
So I try this for 1 validation, 2 transformation(when submitting was my idea)
` .refine(
(val) => {
const allNull = val.x === null && val.y === null && val.z === null;
const allSet = val.x !== null && val.y !== null && val.z !== null;
return allNull || allSet;
},
{
message: "Either all x,y,z must be set, or all must be null",
path: ["x"],
},
)
.transform((val) => {
const allNull = val.x === null && val.y === null && val.z === null;
return allNull ? null : val;
});
zodResolver(formSchema)`
And create types by infering
export type FormValues = z.input<typeof formSchema>; export type ApiValues = z.output<typeof formSchema>;I also override defaultvalues, so if the server sends me a myPoint that is null, I create an empty myPoint (so that the form can handle it), meaning myPoint is never null in the html form.
Components:
<Vector3DComponent lens={lens.focus("myPoint")} label={"Adapter axis point"} />However, here it gets mad because lens.focus it thinks can be null. How can I fix this?
One way would be to move the .transform to the formSchema but that means it would have to look for all vector3d objects, (and I have more of the same).
Runnable example:
https://stackblitz.com/edit/vitejs-vite-plkpv7pz?file=src%2FMyFormComponent.tsx
Edit: If I understand correctly, the useLense and control uses the Api type, not what I thought the FormValues. Is the FormValues only for input values (default values) ? Because I need the form to not have nullable vector3d but turn them into nulls only on submission.
All reactions