# React hook form + zod https://hackmd.io/@henrylenguyen/r1Muy-D4T#Gi%E1%BA%A3i-th%C3%ADch-s%C6%A1-l%C6%B0%E1%BB%A3t-v%E1%BB%81-schema-c%E1%BB%A7a-zod ## 1. Download package ```javascript! yarn add react-hook-form zod @hookform/resolvers ``` // "react-select": "^5.8.0", "@hookform/resolvers": "^3.3.2", ## 2. Method 1 : register - uncontrol component `index.css` file ```css! .error{ color:red; } ``` `App.tsx` file ```javascript! import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { ZodType, z } from "zod"; type FormData = { firstName: string; email: string; age: number; password: string; confirmPassword: string; }; function App() { const schema: ZodType<FormData> = z .object({ firstName: z .string() .min(2, { message: "Longer than 2 char" }) .max(30, { message: "Lower than 30 char" }), email: z.string().email({ message: "Not valid email" }), age: z .number() .min(18, { message: "Min 18" }) .max(100, { message: "Max 100" }) .refine((value) => value !== undefined, { message: "Please enter your age", }), password: z.string().min(5).max(30), confirmPassword: z.string().min(5).max(30), }) .refine((data) => data.password === data.confirmPassword, { message: "Password does not match", path: ["confirmPassword"], }); const { register, handleSubmit, formState: { errors }, } = useForm<FormData>({ resolver: zodResolver(schema), }); const onSubmitData = (data: FormData) => { console.log(data); alert(1); }; return ( <form onSubmit={handleSubmit(onSubmitData)}> <label>First name:</label> <input type="text" {...register("firstName")} /> {errors.firstName && ( <span className="error">{errors.firstName.message}</span> )} <label>Email: </label> <input type="email" {...register("email")} /> {errors.email && <span className="error">{errors.email.message}</span>} <label>Age: </label> <input type="number" {...register("age", { valueAsNumber: true, shouldUnregister: true, })} /> {errors.age && <span className="error">{errors.age.message}</span>} <label>Password: </label> <input type="password" {...register("password")} /> {errors.password && ( <span className="error">{errors.password.message}</span> )} <label>Confirm Password: </label> <input type="password" {...register("confirmPassword")} /> {errors.confirmPassword && ( <span className="error">{errors.confirmPassword.message}</span> )} <button type="submit">SUBMIT</button> </form> ); } export default App; ``` ## 3. Method 2 : controller - Controled component **Installation** ```javascript! npm i zod react-hook-form @hookform/resolvers/zod @hookform/error-message ``` **1. Use in component** ```javascript! import { z } from 'zod' import { SubmitHandler, useForm } from "react-hook-form" import { zodResolver } from '@hookform/resolvers/zod'; import { ErrorMessage } from "@hookform/error-message" const validationSChema = z.object({ email: z.string().min(3).email(), password: z.string().min(3), confirmPassword: z.string().min(3), }).refine((data) => data.password === data.confirmPassword, { message: "Confirm password don't match", path: ["confirmPassword"], }); type FormValues = z.infer<typeof validationSChema>; const TabLogin = () => { const { control, handleSubmit, reset, formState: { errors } } = useForm<FormValues>({resolver: zodResolver(validationSChema)}); const onSubmit: SubmitHandler<FormValues> = async (data) => { console.log(data) }; return ( <form onSubmit={handleSubmit(onSubmit)}> <Input error={errors.email} control={control} name='email' /> <div className="error text-sm"> <ErrorMessage errors={errors} name="email" /> </div> // password, confirmPassword tương tự </form> ``` **2. Custom Input component** ```javascript! import { useController } from "react-hook-form"; type Props = { name: string; control: any; error?: any type?:string }; export const Input = ({name,control,error,type = 'text'}:Props) =>{ const { field } = useController({ name, control, }); return ( <input type={type} value={field.value} onChange={(e) => field.onChange(e.target.value)} /> ) } ``` **3. Custom Checkbox component** ```javascript! import Checkbox from "@material-tailwind/react/components/Checkbox"; import Chip from "@material-tailwind/react/components/Chip"; import { color } from "@material-tailwind/react/types/components/chip"; import { useController } from "react-hook-form"; type Props = { name: string; control: any; options: any[]; }; const Checkbox = ({ options, control, name }: Props) => { const { field } = useController({ control, name, defaultValue: [], }); return options?.map((option) => ( <Chip key={option.id} value={option.name} variant="ghost" color={option.color as color} icon={ <Checkbox value={option.id} onChange={() => { if (field.value.includes(option.id)) { field.onChange( field.value.filter( (v: any) => v.toString() !== option.id.toString() ) ); } else { field.onChange([...field.value, option.id]); } }} checked={field.value.includes(option.id)} color={option.color as color} ripple={false} containerProps={{ className: "p-0" }} crossOrigin={undefined} /> } /> )); }; ``` ## Special case : ### 1. Problem with number input ? Link discussion : https://github.com/colinhacks/zod/discussions/330 &#8599; :::spoiler Cách 1 **Input type number** ```javascript amount: z .string() .refine(v => { let n:any = Number(v); return !isNaN(n) && v?.length > 0} , {message: "Invalid number"}), ``` ::: :::spoiler Cách 2 ```javascript z.object({ age: z.number().optional(), }); <input type="number" {...register("age", { setValueAs: (v) => v === "" ? undefined : parseInt(v, 10), })} />; ``` ::: :::spoiler Cách 3 ```javascript <Input type="number" placeholder="age" {...register("age", { valueAsNumber: true })} /> age: z .number({ invalid_type_error: "age is required" }) .min(1, { message: "1" }), ``` ::: ### 2. Problem with react-select ```javascript "react-hook-form": "^7.48.2", "react-select": "^5.8.0", ``` **A) import** ```javascript import { useForm, Controller } from 'react-hook-form'; import Select from 'react-select'; ``` **B) in component** ```javascript const options = [ { value: '1', label: 'Apple'}, { value: '2', label: 'Ball'}, { value: '3', label: 'Cat'}, ]; function Yourcomponent(props){ const methods = useForm(); const { handleSubmit } = methods; const default_value = 1; return( <> <form onSubmit={handleSubmit(onSumit)} > // another code <Controller control={form.control} // defaultValue={default_value} name="category" render={({ onChange, value, name, ref, field }) => ( <Select {...field} // inputRef={ref} options={options} value={newArr.find(c => c.value === value)} onChange={val => field.onChange(val.value)} /> )} /> {errors.category && ( <span className="error">{errors.category.message}</span> )} // another code </form> </> ) } ``` ## 3. Error Method 1 ```javascript! {errors.description && ( <span className="error">{errors.description.message}</span> )} ``` Method 2 ```javascript! import { ErrorMessage } from "@hookform/error-message"; ``` ```javascript! <ErrorMessage errors={errors} name="description" render={({ message }) => <span className="error">{message}</span>} /> ```