# 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 ↗
:::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>}
/>
```