แฅแ useState แณแญแปแแฃ แ แแตแแแ แช-แจแแฐแญ (re-render) แณแญแแญ โ แ แ แแต useForm hook แฅแป!
Validationแฃ Error handlingแฃ Zod integration โ แแแแ แ แแแ แญแแฉ! ๐
The #1 performant, flexible and extensible forms library for React.
React Hook Form (RHF) โ React แแตแฅ แแญแแฝแ (forms) แแแตแฐแณแฐแญ แจแแซแแแแ แแแแ แแฃแ library แแ! แซแ re-render แซแแฃ แซแ แฅแ state แแจแแแ
โ `useForm()` hookแ แฅแป แตแ แแแแฝแแฃ แแญแแ แแแ แญแแแ!
Built on uncontrolled components โ no re-render on every keystroke, blazing fast!
npm install react-hook-form
แจ Zod แแญ แตแตแ แแ แฐแแ โ npm install @hookform/resolvers zod
const [name, setName] = useState(''); const [email, setEmail] = useState(''); const [errors, setErrors] = useState({}); // โ๏ธ แ แฅแซแแณแแฑ แ แฐแปแ แแฐแ แแ (Key-Stroke) componentแก แ แฒแต (re-render) แญแแแ! <input value={name} onChange={e => setName(e.target.value)} /> <input value={email} onChange={e => setEmail(e.target.value)} /> // โ แ แแ แฐแแ Validation แฅแ Submit แแฐแฅ... แแ แซแต แแณแต แแแข
import { useForm } from 'react-hook-form'; const { register, handleSubmit, formState: { errors } } = useForm(); // โ แ uncontrolled component แแญแ แจแแฐแซ แตแแแ, แ แแแ re-render แจแแแ! <input {...register('name', { required: 'แตแ แซแตแแแแ!' })} /> <input {...register('email', { required: 'แขแแญแ แแตแแฃแต แแดแณ แแ!' })} /> // โ Validation แฅแ Submit โ แแแ แ แ แแต แแญ built-in แแธแ!
register() โ แแแฑแ (Input) แจ React Hook Form แแญ แซแซแญแแแ! แจแแ แ แแ แตแ value, onChange แ แณแตแกแแข แจแแแฑ แตแแ แจ Validation แ แแแฑ แ แแ แฅแแ แญแแแแฃแ!
// {...register()} โ แจแแซแตแแแแแ แแแญ แ แแ แซแซแตแณแ (spreads props) <input {...register('username', { required: 'Username แแแแต แ แแ แต!', minLength: { value: 3, message: 'แขแซแแต 3 แแฐแ แญแแ!' }, maxLength: { value: 20, message: 'แขแ แ 20 แแฐแ แฅแป!' }, pattern: { value: /^[a-zA-Z0-9_]+$/, message: 'แแฐแแ แ แแ แฅแป แแ แจแแแแฐแ!' }, })} /> // แ แแตแฅ แณแแแแต register() แจแแฐแฃแธแ Props แฅแแแ แแธแ: // โ name, ref, onChange, แฅแ onBlur // โ แ แแ แแแ แซแแแแฐ แฃแแ แแฅแแต แ แแถแแฒแญ แญแฐแซแ!
handleSubmit() โ แแญแแ "Submit" แฒแฐแจแ แญแ แแแญแฝแ แแแแชแซ แจ Validation แ แแแต แแจแ แซแธแแ แซแฃแซแแข แแ แแแญ แตแญแญแ แจแแ (passed)แฃ แ แฒแฑแ แณแณ แญแ แแฐ onSubmit แแแญแฝแ แซแแแ!
const { register, handleSubmit } = useForm(); // onSubmit โ แแฐแปแ แ แจแแแด (Valid) แจแแ แฅแป แแ แญแ แแแญแฝแ แจแแฐแซแ! const onSubmit = (data) => { // แ แแ 'data' แแตแฅ แซแแต { username: 'Samuel', email: '...' } แ แญแแต แแธแ console.log('โ แจแแฃแ แณแณ:', data); // โ แแฐ API แจแแแญ แฅแช (API call) แแญแแ แแ แแแญแฃ แแแ แฅแแ แญแแแ! }; // onError โ แ แแน แณแญแแ แตแ แฐแต (failed) แซแ แแ แญแฐแจแ? const onError = (errors) => { console.log('โ แจแฐแแ แฉ แตแ แฐแถแฝ:', errors); }; <form onSubmit={handleSubmit(onSubmit, onError)}> ... </form>
formState โ แจแแญแแ แ แ แแแญ แแแณ แแณแซ แแ! แ แฐแ แแแ แแญ แแ แฅแจแฐแจแแแ แฅแแณแ แฅแแตแณแแ errorsแฃ isSubmittingแฃ isValidแฃ isDirty แ แแณแฐแแต แแฅแฆแฝ แ แแญแแญ แญแฐแฃแฝแแแข
const { register, handleSubmit, formState: { errors, // โต แแฐแป แฒแฐแจแ แตแ แฐแถแน แฅแแ แญแ แซแแแ isSubmitting, // โต แแฐ แฐแญแจแญ แจแแแญ แแฐแต (loading indicator) แแญ แแ แแญ? isValid, // โต แแญแ แแญ แซแแต แแจแแแฝ แ แแ แแ แ แแ แ แแแฐแแ แแญ? isDirty, // โต แฐแ แแแ แแแแชแซ แจแแ แจแ state แแญแฎแณแ (แฝแแ แณแ) แแญ? isSubmitted, // โต แจแแญแ แแจแ แ แฐแณแซ แแแณ แแแฉแ แซแณแซแแข touchedFields, // โต Focus แ แแแฐแ แจแฐแแฉ แแแถแฝแ แแแแจแต แญแจแณแ } } = useForm({ mode: 'onChange' }); // โฆ แตแ แฐแตแ แ แ แตแ-แแแณ แแแณแจแต (Showing Errors conditionally) {errors.email && ( <p style={{color: 'red'}}> {errors.email.message} </p> )}
watch() โ แจ Field แแแแฝ แฒแแ แ Live (แ แแฅแณ) แ แแ แแ แแญ แฅแแฐแฐแจแฑ แแแจแณแฐแ (to observe changes)แข
setValue() โ แจแฎแณแฝแ แ แแฅแณ แ แแแ แจแ แแฑแ field แแ แ แแฎแแซแ แแแแแต!
reset() โ แแญแแ แแ แ แแ แแแ
แณแตแ แแฐ แแแแชแซแ แแแณ แแแแแต! getValues() โ แแญแแ re-render แณแซแฐแญแ แ แแ แซแแตแ แแแแฝ แ แแแซแแแต แแแแ แฅแข
const { register, watch, setValue, reset, getValues } = useForm(); // ๐๏ธ watch() โ field แแตแฅ แ แแ แซแแแ แแจแ แ แแฅแณ แแแจแต const passwordInput = watch('password'); const allInputsFilledOutNow = watch(); // แแแแ แ แ แแต แแญ แแจแต แฒแแแ // โ๏ธ setValue() โ แจแแญแ field-แแฝแ แ แแฎแแซแ แแแแต แฒแซแตแแแ setValue('name', 'Samuel'); setValue('role', 'admin', { shouldValidate: true }); // แแแแ แแแถ Validation แฅแแฒแฐแซ แซแฐแญแแ // ๐ reset() โ แแญแแ แฃแถ แซแฐแญแแแ! reset(); // แแแแ แแแถแฝ แซแธแณแ reset({ name: 'Samuel' }); // แแญแแ แ แฝแตแถ default แแ แญแฐแฃแ // ๐ getValues() โ Re-render แณแซแฐแญแ แจแแแฑแ แแ แแแแ แแแแ แฅ const valuesOnlyForReference = getValues();
แ RHF แแแต แ แญแแต Validation (แแฐแป) แแแ แญ แญแปแแ โ แ แแฐแแ แซแฑ RHF แแตแฅ แซแแตแ แ แซแญแญ Built-in Rules (แแตแฃแ แ แแแต) แแ แแ แฒแแแค แแแ แฐแแ แญแ แแฅ แแแแแ แแญแแ แจแแแแ แจ Zod Schema (แ zodResolver แ แแซแแแต) แแแจแฅ แตแฝแแแฝแ!
const { register, handleSubmit, formState: { errors } } = useForm(); // แแญแ แฒแซแแแ RHF แจแซแฑแ (Native html-like rules) แญแฐแ แแแข แ แแแแ แฅแแฐแแ แญแแตแแ: <input {...register('email', { required: 'แญแ แ แณแณแแ แแแ แ แตแฝแแ!', pattern: { value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, message: 'แซแตแแกแต แจแขแแญแ แ แตแซแป แแญ แ แญแฐแแ!' } })} /> {errors.email && <span>{errors.email.message}</span>} <input type="password" {...register('password', { required: 'แแตแฅแซแ แแฅแญแแ แซแซแฑ (Required)!', minLength: { value: 8, message: 'แขแซแแต 8 แแฐแ แแแ แ แแ แต!' }, validate: (v) => v !== 'password' || 'แฅแแฒแ แ แแแ แจแแแแต แ แญแแแตแ!' })} />
import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; // 1. แจแแฐแแ P3ยทL02 แจแฐแแซแฝแแต Schema แญแ แแ (แ ZOD แ แ แฅแแแแฃแแ) const RegisterSchema = z.object({ name: z.string().min(2, 'แจแตแ แญแแแต แขแซแแต 2 แแแ แ แแ แต'), email: z.string().email('แตแญแญแแ แขแแญแ แ แญแฐแแ'), password: z.string().min(8, 'Password แขแซแแต 8 แแแ แ แแ แต'), confirm: z.string() }).refine(data => data.password === data.confirm, { // แแแฑแแ Input แแแแฝ แฅแซแแแแ 'Password Match' แแตแจแแ แซแจแแแฃแ: message: 'แซแตแแกแต แแ-แแ แญแ แฅแญแต แ แญแฑ แ แญแแณแฐแแ!', path: ['confirm'], }); type RegisterFormDataType = z.infer<typeof RegisterSchema>; // 2. แ ZOD แจแฐแแ แจแ Validation แแญ useForm แแแแแต const { register, handleSubmit, formState: { errors } } = useForm<RegisterFormDataType>({ resolver: zodResolver(RegisterSchema), // แจแแ แญแแแ schema แฅแแ แฅแแตแแฃแแ mode: 'onChange', // แญแ แจ 'Real-Time' แแแฝ แฅแแตแแแ แซแตแฝแแ }); // 3. register แ แ แซแฑ input แ แแฑ แแตแฅ แตแแซแฐแ แตแ Rule แแจแแ แ แญแ แ แ แฅแแ <input {...register('email')} /> {errors.email && <p>{errors.email.message}</p>}
| แจ Validation แแแซ (Rule) | แตแญแแ แฅแ แแผ แฅแ แ แแญ แญแแแ? | แฅแแดแต แฅแแแแ? (แแณแ) |
|---|---|---|
| required | แแแฑ แฃแถ แแ แฅแแณแซแแ แแแแฐแฅ แญแแแ! | required: 'แญแ แแแต แแแแต แ แแ แต!' |
| minLength | แจแฝแแ แญแแแต แจแฐแแแธแ แแฅแญ แฅแแณแซแแต แซแฐแญแแ | minLength: {value:3, message:'แขแซแแต 3 แแฐแ แญแปแ!'} |
| maxLength | แจแฝแแ แญแแแต แจแฐแฐแ แ แฃแชแซ (MAX) แแฅแแฅ แฅแแฐแแแ แต | maxLength: {value:50, message:'แจ 50 แแฐแ แแฅแแฅ แจแแ แตแ!'} |
| min | แแแฅแญ (Number fields) แแแจแ แจแแฝแแแ แ แแตแฐแ แแ แ แญแแตแแ | min: {value:18, message:'แแตแแ แจ 18 แแแต แจแแ แตแ!'} |
| max | แแแฅแญ แฅแ แแแแต แแแแต แจแแฝแแแ แจแแฐแ แแ แ แญแแตแแ | max: {value:120, message:'แจแแฐแแ แแ แ 120 แแ!'} |
| pattern | แฝแแ แจแฐแฐแ แแ Regex Pattern แแจแฐแแ แซแจแแแฃแ | pattern: {value: /^[a-z]+$/, message:'แฅแฃแญแ แ แตแแฝ แแฐแแต แฅแป แญแปแ!'} |
| validate | แจแซแณแฝแแ Custom function แฝแแ แแญแแ แแแแฐแฝ แซแแแแแ | validate: v => v !== 'admin' || 'แญแ แจแฐแจแแจแ แตแ แแ!' |
Controller โ RHFแ แฅแแฐ Material UIแฃ Chakra UI แจแแณแฐแ 3rd party UI libraries แแญ แตแตแ แแ register() แฅแปแแ แ แ แ แญแแแแข แตแแแ
Controllerแ แตแ แแแแฝแแข แญแแ แ Custom Field แ แตแแแ แจแแแตแ (onChange, onBlur, value) แซแแฅแแแแข
import { useForm, Controller } from 'react-hook-form'; // 'control' แจแฐแฃแแ แแแ แแแญ Controllerแ แจ useForm แแญ แซแซแญแแแ! const { control, handleSubmit } = useForm(); <Controller name="role" control={control} defaultValue="user" rules={{ required: 'แฅแฃแญแ แ แแต แแ แญแแจแก!' }} // render แแฎแ แแฐแแฃแฉ Component (MUI, Chakra) แ แตแแแ แแแฎแฝแ แซแแฅแแ render={({ field, fieldState }) => ( <CustomSelect {...field} // onChange, onBlur, value, แฅแ name แฅแแ แแตแฅ แญแแแ options={['admin','user','moderator']} error={fieldState.error?.message} // แจแตแ แฐแต แแแแญแต แซแ แฅแแ แญแฐแแแแ /> )} />
import { useForm, useFieldArray } from 'react-hook-form'; const { control, register } = useForm({ defaultValues: { skills: [{ name: '' }] } }); // Dynamic Fields (แฐแแแแญ แแแถแฝ) โ แแญแแญ แแญ แ แฒแต แแแต แแแจแแญ (append) แแญแ แแแฅแแต (remove) แญแ แ แแ! const { fields, append, remove } = useFieldArray({ control, name: 'skills', }); {fields.map((field, index) => ( <div key={field.id}> <input {...register(`skills.${index}.name`)} // skills.0.name, skills.1.name แฅแซแ แญแแแแฃแ placeholder="แแ แฝแแณ แ แแแต?" /> <button onClick={() => remove(index)}>๐๏ธ แ แตแแแต</button> </div> ))} <button onClick={() => append({ name: '' })}> + แ แฒแต แฝแแณ แจแแญ </button>
Mode โ Validation แแผ แฅแแฒแฐแซ แฅแแฐแแตแแแ แจแแตแแตแแ แต แแ (แแแณแแก แฒแแแฃ focus แฒแ แแฃ แแแฐ)แข
import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; // 1๏ธโฃ แจ Zod Schema แแแแแต const Schema = z.object({ name: z.string().min(2, 'แขแซแแต 2 แแฐแ แแแ แ แแ แต'), email: z.string().email('แตแญแญแแ แจแขแแญแ แ แตแซแป แซแตแแก'), password: z.string().min(8, 'แจแญแแ แแ แขแซแแต 8 แแฐแ แแแ แ แแ แต'), }); type FormData = z.infer<typeof Schema>; export default function RegisterForm() { const { register, handleSubmit, formState: { errors, isSubmitting }, reset, } = useForm<FormData>({ resolver: zodResolver(Schema), // โ Zodแ แจ RHF แแญ แซแแแแ mode: 'onChange', // โ Validation แ แ แฝแ แต แฅแแฒแฐแซ แซแฐแญแแ }); // 3๏ธโฃ Submit แฒแฐแจแ แจแแฐแซ แแแญแฝแ const onSubmit = async (data: FormData) => { await axios.post('/api/register', data); reset(); // โ แฒแณแซ แแญแแ แซแธแณแ }; return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register('name')} placeholder="แแ แตแ" /> {errors.name && <p>{errors.name.message}</p>} <input {...register('email')} placeholder="แขแแญแ" /> {errors.email && <p>{errors.email.message}</p>} <input {...register('password')} type="password" placeholder="แจแญแแ แแ" /> {errors.password && <p>{errors.password.message}</p>} <button type="submit" disabled={isSubmitting}> {isSubmitting ? 'โณ แ แแแแแฅ แแญ...' : 'โ แญแแแแก'} </button> </form> ); }
แแญแแฝแ แแแตแฐแณแฐแญ registerแฃ handleSubmit แฅแ formState แจแฐแฃแ แแ แแแแแซแแฝแ แ แ แแต แแญ แญแฐแ แแแข
แจแแแขแซ แแญแแฝแ (Inputs) แจ React Hook Form แแญ แจแแซแแแ แฅแ แฅแแฐ onChange แฅแ onBlur แซแ แแฎแแฝแ แ Spread Operator {...register('name')} แจแแซแตแฐแแแ แแ แแแญแฝแ แแแข
Zodแ แ แแ แแ แ แแซแซ แจแแฐแป แฐแแฆแฝแ (Schema) แแปแ แฅแ แ zodResolver แ แแซแแแต แจ React Hook Form แแญ แซแแแแ แฅแแจแ แแแแแต แญแปแแแข
TypeScriptแ แฅแ Runtime Validationแ แ แ แแต แแญ แแแแแต แญแจแณแ!แ useForm แแญแซแแฝ แแตแฅ onSubmit (แฒแแญ แฅแป)แฃ onBlur (แแแฑ แฐแแ แฒแแฃ) แแญแ onChange (แฅแซแแณแแฑ แแฐแ แฒแปแ) แแฐแปแ แฅแแฒแฐแซ แแตแฐแซแจแ แญแปแแแข
แฅแแฐ แแฎแแญแฑ แแแแต แจแฐแ แแแแ แแแต แแแปแปแ แญแ แ แแ!แแฐแ แ แซแแแ แแญแ แจแแญ UI แแญแฅแจแชแแฝแ (แฅแแฐ Material UI แซแ) แจ RHF แแญ แแแแแแต แฅแ แจแแญแ แณแณแ แ แตแญแญแ แแแตแฐแแแ แ Controller แ แฉแ แแแ แแดแณ แแแข
แแฅแฅแญ แจแแฐแจแแฃแธแแ (Controlled) แจแแญ แฎแแแแแถแฝ แแแตแฐแณแฐแญ แญแ แ แแ!register() แตแซแ แแแตแ แแ?