<
>
ETHIOCODE
SOFTSELECT
แˆถแแ‰ตแ‹Œแˆญ แˆแˆญแŒซ แˆˆแŠขแ‰ตแ‹ฎแŒตแ‹ซ
P3 ยท L04 ยท REACT HOOK FORM ยท FORMS ยท LOADING...
PHASE 3 ยท LESSON 04

React Hook Form โ€”
แŽแˆญแˆ แˆ˜แˆตแˆชแ‹ซแ‹ แ‰€แˆ‹แˆ‰ แˆ˜แŠ•แŒˆแ‹ต! ๐Ÿ“‹

แ‰ฅแ‹™ useState แˆณแ‹ญแŒปแแฃ แŠ แˆ‹แˆตแˆแˆ‹แŒŠ แˆช-แˆจแŠ•แ‹ฐแˆญ (re-render) แˆณแ‹ญแŠ–แˆญ โ€” แ‰ แŠ แŠ•แ‹ต useForm hook แ‰ฅแ‰ป!
Validationแฃ Error handlingแฃ Zod integration โ€” แˆแˆ‰แŠ•แˆ แ‰ แ‰€แˆ‹แˆ แ‹ญแˆ›แˆฉ! ๐Ÿš€
The #1 performant, flexible and extensible forms library for React.

แ‹ซแ‹˜แŒ‹แŒ€แ‹: @AppMinds_ET
01React Hook Form แˆแŠ•แ‹ตแŠ• แАแ‹?

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

โŒ แ‹ซแˆˆ RHF แˆฒแˆฐแˆซ โ€” แ‰ฅแ‹™ state แŠฅแŠ“ แ‰ฐแ‹ฐแŒ‹แŒ‹แˆš re-render
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 แˆ›แˆฐแ‰ฅ... แˆŒแˆ‹ แˆซแˆต แˆแ‰ณแ‰ต แАแ‹แข
โœ… React Hook Form โ€” แ‰€แˆ‹แˆ แŠ แˆฐแˆซแˆญ!
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 แŠ“แ‰ธแ‹!
02โš™๏ธ useForm Hook โ€” แ‹จแŽแˆญแˆ แˆแŠ”แ‰ณ (Form State Visualizer)
Form State แˆ›แ‰ฅแˆซแˆชแ‹ซ โ€” แŒ แ‰… แŠ แ‹ตแˆญแŒ‰ โ†“
RHF แ‹แˆตแŒฅ formState แˆแŠ• แ‹ญแˆ˜แˆตแˆ‹แˆ?
๐Ÿ’ค
IDLE
โ†’
โœ๏ธ
DIRTY
โ†’
โŒ
INVALID
โ†’
โœ…
VALID
โ†’
๐Ÿš€
SUBMIT
03register ยท handleSubmit ยท formState โ€” แ‹‹แŠ“ APIแ‹Žแ‰ฝ
useForm.tsx
CORE API
register()
handleSubmit()
formState
watch ยท setValue

register() โ€” แŠแˆแ‹ฑแŠ• (Input) แŠจ React Hook Form แŒ‹แˆญ แ‹ซแ‹ซแ‹ญแ‹˜แ‹‹แˆ! แŠจแ‹šแˆ… แ‰ แŠ‹แˆ‹ แˆตแˆˆ value, onChange แŠ แ‰ณแˆตแ‰กแˆแข แ‹จแŠแˆแ‹ฑ แˆตแˆแŠ“ แ‹จ Validation แˆ…แŒแŒ‹แ‰ฑ แ‰ แˆ™แˆ‰ แŠฅแ‹šแˆ… แ‹ญแˆ˜แ‹˜แŒˆแ‰ฃแˆ‰!

register.tsx
// {...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 แˆแŠ•แŠญแˆฝแŠ• แ‹ซแˆแ‹แˆ!

handleSubmit.tsx
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 แ‰ แˆ˜แˆณแˆฐแˆ‰แ‰ต แАแŒฅแ‰ฆแ‰ฝ แ‰ แ‹แˆญแ‹แˆญ แ‹ญแˆฐแŒฃแ‰ฝแŠ‹แˆแข

formState.tsx
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 แˆณแ‹ซแ‹ฐแˆญแŒ‰ แŠ แˆแŠ• แ‹ซแˆ‰แ‰ตแŠ• แ‹‹แŒ‹แ‹Žแ‰ฝ แ‰ แŒŠแ‹œแ‹ซแ‹ŠแАแ‰ต แˆˆแˆ›แŠ•แ‰ แ‰ฅแข

watch-setValue-reset.tsx
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();
04๐Ÿ›ก๏ธ Validation โ€” แˆ˜แˆแ‰ฐแˆป แˆ…แŒŽแ‰ฝ แŠฅแŠ“ แ‹จ Zod แŠ แŒ แ‰ƒแ‰€แˆ

แ‰  RHF แˆแˆˆแ‰ต แŠ แ‹ญแАแ‰ต Validation (แแ‰ฐแˆป) แˆ˜แแŒ แˆญ แ‹ญแ‰ปแˆ‹แˆ โ€” แŠ แŠ•แ‹ฐแŠ›แ‹ แˆซแˆฑ RHF แ‹แˆตแŒฅ แ‹ซแˆ‰แ‰ตแŠ• แŠ แŒซแŒญแˆญ Built-in Rules (แ‹แˆตแŒฃแ‹Š แˆ…แŒแŒ‹แ‰ต) แˆ˜แŒ แ‰€แˆ แˆฒแˆ†แŠ•แค แˆŒแˆ‹แ‹ แ‹ฐแŒแˆž แ‹ญแ‰ แˆแŒฅ แ‹˜แˆ˜แŠ“แ‹ŠแŠ“ แŠƒแ‹ญแˆˆแŠ› แ‹จแˆ†แАแ‹แŠ• แ‹จ Zod Schema (แ‰  zodResolver แŠ แˆ›แŠซแŠแАแ‰ต) แˆ˜แˆแˆจแŒฅ แ‰ตแ‰ฝแˆ‹แˆ‹แ‰ฝแˆ!

validation.tsx
VALIDATION
๐Ÿ“‹ แŠ แŒซแŒญแˆญ แˆ…แŒแŒ‹แ‰ถแ‰ฝ (Built-in Rules)
๐Ÿ›ก๏ธ แˆˆแ‰ตแˆแˆแ‰… Validation: Zod Resolver
๐Ÿ“Š แ‹จ Validation Rules แ‹แˆญแ‹แˆญ
built-in-rules.tsx
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'
              || 'แŠฅแŠ•แ‹ฒแˆ… แ‰ แ‰€แˆ‹แˆ‰ แ‹จแˆšแŒˆแˆ˜แ‰ต แŠ แ‹ญแˆแ‰€แ‹ตแˆ!'
  })} />
zod-resolver.tsx
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' || 'แ‹ญแˆ… แ‹จแ‰ฐแŠจแˆˆแŠจแˆˆ แˆตแˆ แАแ‹!'
05๐ŸŽฎ แ‰€แŒฅแ‰ณ แˆ˜แˆžแŠจแˆชแ‹ซ โ€” Live Form Demo!
แ‹จแˆแ‹แŒˆแ‰ฃ (Registration) แŽแˆญแˆ แˆฒแˆ™แˆŒแˆฝแŠ•!
๐Ÿ’ค IDLE
๐Ÿ“ แ‹จ แˆแ‹แŒˆแ‰ฃ Form!
z.string().min(2) โ€” แ‰ขแ‹ซแŠ•แˆต 2 แŠแ‹ฐแˆ
z.string().email() โ€” แ‰ตแŠญแŠญแˆˆแŠ› format แ‹ญแƒแ‰
z.string().min(8) โ€” แ‰ขแ‹ซแŠ•แˆต 8 แ‰แˆแŠแ‹Žแ‰ฝ แ‹ซแˆตแˆแˆแŒ‹แˆ‰
.refine() โ€” แŠจแˆ‹แ‹ญ แŠจแˆžแˆ‰แ‰ต แ‹จแ‹ญแˆˆแ แ‰ƒแˆ แŒ‹แˆญ แ‰ฐแˆ˜แˆณแˆณแ‹ญ แˆ˜แˆ†แŠ• แŠ แˆˆแ‰ แ‰ต
โœ… แ‰ แ‰ฐแˆณแŠซ แˆแŠ”แ‰ณ Submit แ‰ฐแ‹ฐแˆญแŒ“แˆ! (Success)!
โšก แ‹จ formState แŠฅแŠ•แ‰…แˆตแ‰ƒแˆด แ‰€แŒฅแ‰ณ แŠฅแ‹ญแ‰ณ (Live State)
isValidfalse
isDirtyfalse
isSubmittingfalse
isSubmittedfalse
errors.nameundefined
errors.emailundefined
errors.passwordundefined
errors.confirmundefined
๐Ÿ‘๏ธ แ‹จแŒˆแ‰กแ‰ตแŠ• แ…แˆแŽแ‰ฝ watch() แ‰ แˆ›แ‹ตแˆจแŒ แ‰ แ‰€แŒฅแ‰ณ แˆ˜แŠจแ‰ณแ‰ฐแˆ
name""
email""
password""
06๐Ÿ”ฌ Controller โ€” UI Libraries แŒ‹แˆญ แˆ›แŒˆแŠ“แŠ˜แ‰ต

Controller โ€” RHFแŠ• แŠฅแŠ•แ‹ฐ Material UIแฃ Chakra UI แŠจแˆ˜แˆณแˆฐแˆ‰ 3rd party UI libraries แŒ‹แˆญ แˆตแ‰ตแŒ แ‰€แˆ™ register() แ‰ฅแ‰ปแ‹แŠ• แ‰ แ‰‚ แŠ แ‹ญแˆ†แŠ•แˆแข แˆตแˆˆแ‹šแˆ… ControllerแŠ• แ‰ตแŒ แ‰€แˆ›แˆ‹แ‰ฝแˆแข แ‹ญแˆ„แˆ แˆˆ Custom Field แŠ แˆตแˆแˆ‹แŒŠ แ‹จแˆ†แŠ‘แ‰ตแŠ• (onChange, onBlur, value) แ‹ซแ‰€แ‰ฅแˆˆแŠ“แˆแข

controller.tsx
CONTROLLER
Controller
useFieldArray
Form Validation Modes
Controller.tsx
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} // แ‹จแˆตแˆ…แ‰ฐแ‰ต แˆ˜แˆแ‹•แŠญแ‰ต แŠซแˆˆ แŠฅแ‹šแˆ… แ‹ญแ‰ฐแˆ‹แˆˆแ‹แˆ
    />
  )}
/>
useFieldArray.tsx
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 แˆฒแŒ แ‹แฃ แ‹ˆแ‹˜แ‰ฐ)แข

07โšก Production-Ready Setup (แŠ แŒ แ‰ƒแˆ‹แ‹ญ แŠ แˆฐแˆซแˆญ)
RegisterForm.tsx
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>
  );
}
๐Ÿ“ŒแŒ แ‰…แˆˆแˆ แ‹ซแˆˆ แˆ›แˆตแ‰ณแ‹ˆแˆป
1
useForm() โ€” แ‹จแŽแˆญแˆ›แ‰ฝแŠ• แˆ˜แАแˆป

แŽแˆญแˆžแ‰ฝแŠ• แˆˆแˆ›แˆตแ‰ฐแ‹ณแ‹ฐแˆญ registerแฃ handleSubmit แŠฅแŠ“ formState แ‹จแ‰ฐแ‰ฃแˆ‰ แ‹‹แŠ“ แˆ˜แŒˆแˆแŒˆแ‹ซแ‹Žแ‰ฝแŠ• แ‰ แŠ แŠ•แ‹ต แˆ‹แ‹ญ แ‹ญแˆฐแŒ แŠ“แˆแข

แ‹ซแˆˆ แˆแŠ•แˆ Context Provider แ‰ แ‰€แŒฅแ‰ณ แ‰  Hook แˆ˜แŒ แ‰€แˆ แ‹ญแ‰ปแˆ‹แˆ!
2
register() โ€” InputแŠ• แŠจ RHF แŒ‹แˆญ แˆ›แ‹ซแ‹ซแ‹ฃ

แ‹จแˆ˜แŒแ‰ขแ‹ซ แŽแˆญแˆžแ‰ฝแŠ• (Inputs) แŠจ React Hook Form แŒ‹แˆญ แ‹จแˆšแ‹ซแŒˆแŠ“แŠ แŠฅแŠ“ แŠฅแŠ•แ‹ฐ onChange แŠฅแŠ“ onBlur แ‹ซแˆ‰ แ•แˆฎแ–แ‰ฝแŠ• แ‰  Spread Operator {...register('name')} แ‹จแˆšแ‹ซแˆตแ‰ฐแˆ‹แˆแ แ‹‹แŠ“ แˆแŠ•แŠญแˆฝแŠ• แАแ‹แข

แŽแˆญแˆ™ uncontrolled แŠฅแŠ•แ‹ฒแˆ†แŠ• แ‰ แˆ›แ‹ตแˆจแŒ แŠ แˆ‹แˆตแˆแˆ‹แŒŠ Re-renderแŠ• แ‹ญแŠจแˆ‹แŠจแˆ‹แˆ!
3
Zod + zodResolver โ€” แˆแˆญแŒก แ‹จ Validation แŒฅแˆแˆจแ‰ต

ZodแŠ• แ‰ แˆ˜แŒ แ‰€แˆ แŒ แŠ•แŠซแˆซ แ‹จแแ‰ฐแˆป แ‹ฐแŠ•แ‰ฆแ‰ฝแŠ• (Schema) แˆ˜แŒปแ แŠฅแŠ“ แ‰  zodResolver แŠ แˆ›แŠซแŠแАแ‰ต แŠจ React Hook Form แŒ‹แˆญ แ‹ซแˆˆแˆแŠ•แˆ แŠฅแŠ•แŠจแŠ• แˆ›แŒˆแŠ“แŠ˜แ‰ต แ‹ญแ‰ปแˆ‹แˆแข

TypeScriptแŠ• แŠฅแŠ“ Runtime ValidationแŠ• แ‰ แŠ แŠ•แ‹ต แˆ‹แ‹ญ แˆˆแˆ›แŒแŠ˜แ‰ต แ‹ญแˆจแ‹ณแˆ!
4
Mode โ€” Validation แˆ˜แ‰ผ แ‹ญแˆฐแˆซ?

แ‰  useForm แˆแˆญแŒซแ‹Žแ‰ฝ แ‹แˆตแŒฅ onSubmit (แˆฒแˆ‹แŠญ แ‰ฅแ‰ป)แฃ onBlur (แŠแˆแ‹ฑ แ‰ฐแˆˆแ‰† แˆฒแ‹ˆแŒฃ) แ‹ˆแ‹ญแˆ onChange (แŠฅแ‹ซแŠ•แ‹ณแŠ•แ‹ฑ แŠแ‹ฐแˆ แˆฒแŒปแ) แแ‰ฐแˆปแ‹ แŠฅแŠ•แ‹ฒแˆฐแˆซ แˆ›แˆตแ‰ฐแŠซแŠจแˆ แ‹ญแ‰ปแˆ‹แˆแข

แŠฅแŠ•แ‹ฐ แ•แˆฎแŒ€แŠญแ‰ฑ แแˆ‹แŒŽแ‰ต แ‹จแ‰ฐแŒ แ‰ƒแˆšแ‹แŠ• แˆแˆแ‹ต แˆˆแˆ›แˆปแˆปแˆ แ‹ญแŒ แ‰…แˆ›แˆ!
5
Controller โ€” แˆˆ 3rd Party UI Libraries

แˆ˜แ‹ฐแ‰ แŠ› แ‹ซแˆแˆ†แŠ‘ แ‹ˆแ‹ญแˆ แ‹จแ‹แŒญ UI แˆ‹แ‹ญแ‰ฅแˆจแˆชแ‹Žแ‰ฝแŠ• (แŠฅแŠ•แ‹ฐ Material UI แ‹ซแˆ‰) แŠจ RHF แŒ‹แˆญ แˆˆแˆ›แŒˆแŠ“แŠ˜แ‰ต แŠฅแŠ“ แ‹จแŽแˆญแˆ แ‹ณแ‰ณแŠ• แ‰ แ‰ตแŠญแŠญแˆ แˆˆแˆ›แˆตแ‰ฐแˆ‹แˆˆแ แ‰  Controller แ‰ แŠฉแˆ แˆ›แˆˆแ แŒแ‹ดแ‰ณ แАแ‹แข

แ‰แŒฅแŒฅแˆญ แ‹จแˆšแ‹ฐแˆจแŒแ‰ฃแ‰ธแ‹แŠ• (Controlled) แ‹จแ‹แŒญ แŠฎแˆแ–แАแŠ•แ‰ถแ‰ฝ แˆˆแˆ›แˆตแ‰ฐแ‹ณแ‹ฐแˆญ แ‹ญแŒ แ‰…แˆ›แˆ!
๐ŸŽฏแ‹จแ‹•แ‹แ‰€แ‰ต แˆ˜แˆแ‰ฐแŠ› (Quiz)
โ“ React Hook Form แ‹แˆตแŒฅ register() แˆตแˆซแ‹ แˆแŠ•แ‹ตแŠ• แАแ‹?
What is the primary role of register() in React Hook Form?
A แŽแˆญแˆ™แŠ• Submit แˆˆแˆ›แ‹ตแˆจแŒ แŠจ API server แŒ‹แˆญ แŒแŠ•แŠ™แАแ‰ต แˆ˜แแŒ แˆญ
B Component-แ‹ แˆแˆแŒŠแ‹œ re-render แŠฅแŠ•แ‹ฒแ‹ซแ‹ฐแˆญแŒ แˆ›แˆตแŒˆแ‹ฐแ‹ต
C แŠแˆแ‹ฑแŠ• แŠจ RHF แŒ‹แˆญ แˆ›แ‹ซแ‹ซแ‹ โ€” name, ref, onChange, onBlur propsแŠ• แˆˆ input แˆ›แ‰€แ‰ แˆ โœ…
D แ‹จ Zod SchemaแŠ• แˆแŒฅแˆฎ Validation แˆ˜แˆตแˆซแ‰ต