ฟิลด์ที่อยู่สำหรับ react-hook-form
ครอบ cascade select ด้วย Controller ของ react-hook-form เพื่อผูกกับ validation และ state ของฟอร์มที่มีอยู่แล้ว
ThaiAddressFormField เหมาะกับฟอร์มที่ใช้ react-hook-form อยู่แล้วและต้องการผูกที่อยู่เข้ากับ state ของฟอร์มโดยตรง แทนที่จะอ่านค่าจาก hidden inputs รูปแบบ prop ต่างจากคอมโพเนนต์อื่นโดยตั้งใจ: ส่ง control/name/rules เหมือนกับ useController ของ react-hook-form เอง และไม่มี value/onValueChange/hidden inputs
'use client'
import { useState } from 'react'
import { useForm } from 'react-hook-form'
import type { ResolvedThaiAddress } from 'thaizip'
import { ThaiAddressFormField } from '@/components/thai-address-form-field'
type FormValues = {
address: ResolvedThaiAddress | null
}
export default function AddressFormFieldDemo() {
const { control, handleSubmit } = useForm<FormValues>({ defaultValues: { address: null } })
const [submitted, setSubmitted] = useState<ResolvedThaiAddress | null>(null)
return (
<form className="flex flex-col gap-3" onSubmit={handleSubmit((data) => setSubmitted(data.address))}>
<ThaiAddressFormField
control={control}
name="address"
/>
<div>
<button type="submit" className="rounded-md border border-input bg-background px-3 py-1.5 text-sm hover:bg-accent">
ส่งฟอร์ม
</button>
</div>
<p className="text-sm font-medium">ค่าที่ส่ง</p>
<pre className="max-h-72 overflow-auto rounded-md border bg-muted p-3 text-xs">{JSON.stringify(submitted, null, 2)}</pre>
</form>
)
}ติดตั้ง
npx react-thaizip add address-form-fieldCLI เลือกไฟล์ให้ตาม style ที่ init บันทึกไว้: โปรเจกต์ที่มี components.json ของ shadcn/ui จะได้ไฟล์ที่ประกอบจาก primitives ของ shadcn (Base UI, Radix หรือ React Aria) นอกนั้นได้แบบ vanilla
วิธีใช้
import { useForm } from 'react-hook-form'
import type { ResolvedThaiAddress } from 'thaizip'
import { ThaiAddressFormField } from '@/components/thai-address-form-field'const { control, handleSubmit } = useForm<{ address: ResolvedThaiAddress | null }>({
defaultValues: { address: null },
})<ThaiAddressFormField control={control} name="address" />ฟิลด์ name ที่ระบุ ("address" ในตัวอย่าง) ต้องมีชนิดค่าเป็น ResolvedThaiAddress | null ใน type ของฟอร์ม handleSubmit ของ react-hook-form อ่านค่าได้โดยตรง คอมโพเนนต์นี้ไม่ render hidden inputs
ตัวอย่าง
กรอกข้อมูลภาษาอังกฤษ
ใช้ locale="en" เพื่อแสดงป้ายและตัวเลือกของ cascade select ที่ฝังอยู่เป็นภาษาอังกฤษ
'use client'
import { useState } from 'react'
import { useForm } from 'react-hook-form'
import type { ResolvedThaiAddress } from 'thaizip'
import { ThaiAddressFormField } from '@/components/thai-address-form-field'
type FormValues = {
address: ResolvedThaiAddress | null
}
export default function AddressFormFieldEn() {
const { control, handleSubmit } = useForm<FormValues>({ defaultValues: { address: null } })
const [submitted, setSubmitted] = useState<ResolvedThaiAddress | null>(null)
return (
<form className="flex flex-col gap-3" onSubmit={handleSubmit((data) => setSubmitted(data.address))}>
<ThaiAddressFormField
control={control}
name="address"
locale="en"
/>
<div>
<button type="submit" className="rounded-md border border-input bg-background px-3 py-1.5 text-sm hover:bg-accent">
Submit
</button>
</div>
<p className="text-sm font-medium">Submitted value</p>
<pre className="max-h-72 overflow-auto rounded-md border bg-muted p-3 text-xs">{JSON.stringify(submitted, null, 2)}</pre>
</form>
)
}ตรวจสอบด้วย validation rule
ส่ง rules แบบเดียวกับที่ส่งให้ Controller/useController ของ react-hook-form ตัวอย่างนี้กำหนดให้ต้องเลือกที่อยู่ครบก่อนส่งฟอร์ม ข้อความ error จะแสดงเป็น <p role="alert"> ใต้ cascade select
'use client'
import { useState } from 'react'
import { useForm } from 'react-hook-form'
import type { ResolvedThaiAddress } from 'thaizip'
import { ThaiAddressFormField } from '@/components/thai-address-form-field'
type FormValues = {
address: ResolvedThaiAddress | null
}
export default function AddressFormFieldValidation() {
const { control, handleSubmit } = useForm<FormValues>({ defaultValues: { address: null } })
const [submitted, setSubmitted] = useState<ResolvedThaiAddress | null>(null)
return (
<form className="flex flex-col gap-3" onSubmit={handleSubmit((data) => setSubmitted(data.address))}>
<ThaiAddressFormField
control={control}
name="address"
rules={{ validate: (value) => value !== null || 'กรุณาเลือกที่อยู่ให้ครบ' }}
/>
<div>
<button type="submit" className="rounded-md border border-input bg-background px-3 py-1.5 text-sm hover:bg-accent">
ส่งฟอร์ม
</button>
</div>
<p className="text-sm font-medium">ค่าที่ส่ง</p>
<pre className="max-h-72 overflow-auto rounded-md border bg-muted p-3 text-xs">{JSON.stringify(submitted, null, 2)}</pre>
</form>
)
}ปิดใช้งาน
ส่ง disabled เพื่อปิดทุก select ใน cascade select ที่ฝังอยู่
'use client'
import { useForm } from 'react-hook-form'
import type { ResolvedThaiAddress } from 'thaizip'
import { ThaiAddressFormField } from '@/components/thai-address-form-field'
type FormValues = {
address: ResolvedThaiAddress | null
}
export default function AddressFormFieldDisabled() {
const { control } = useForm<FormValues>({ defaultValues: { address: null } })
return <ThaiAddressFormField control={control} name="address" disabled />
}ใช้ในฟอร์ม
ฟิลด์ที่อยู่คู่กับ input ทั่วไปที่ใช้ register ในฟอร์มเดียวกัน ทั้งสองช่องตรวจสอบตอนส่ง และ handleSubmit ได้ค่าทั้งหมดพร้อมกัน
'use client'
import { useState } from 'react'
import { useForm } from 'react-hook-form'
import type { ResolvedThaiAddress } from 'thaizip'
import { ThaiAddressFormField } from '@/components/thai-address-form-field'
type FormValues = {
name: string
address: ResolvedThaiAddress | null
}
export default function AddressFormFieldFull() {
const { control, register, handleSubmit, formState: { errors } } = useForm<FormValues>({
defaultValues: { name: '', address: null },
})
const [submitted, setSubmitted] = useState<FormValues | null>(null)
return (
<form className="flex flex-col gap-4" onSubmit={handleSubmit(setSubmitted)}>
<label className="flex flex-col gap-1.5 text-sm font-medium">
ชื่อ-นามสกุล
<input
{...register('name', { required: 'กรุณากรอกชื่อ' })}
aria-invalid={errors.name ? true : undefined}
className="h-9 rounded-md border border-input bg-background px-3 text-sm"
/>
{errors.name && <span role="alert" className="text-sm font-normal text-destructive">{errors.name.message}</span>}
</label>
<ThaiAddressFormField
control={control}
name="address"
rules={{ validate: (value) => value !== null || 'กรุณาเลือกที่อยู่ให้ครบ' }}
/>
<div>
<button type="submit" className="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90">
สมัครสมาชิก
</button>
</div>
<p className="text-sm font-medium">ค่าที่ส่ง</p>
<pre className="max-h-72 overflow-auto rounded-md border bg-muted p-3 text-xs">{JSON.stringify(submitted, null, 2)}</pre>
</form>
)
}API Reference
| Prop | Type | ค่าเริ่มต้น | รายละเอียด |
|---|---|---|---|
control | Control<TFieldValues> | — | จำเป็น ค่า control จาก useForm() ของ react-hook-form |
name | FieldPath<TFieldValues> | — | จำเป็น path ของฟิลด์ในฟอร์ม ชนิดค่าต้องเป็น ResolvedThaiAddress | null |
rules | Omit<RegisterOptions<…>, 'valueAsNumber' | 'valueAsDate' | 'setValueAs' | 'disabled'> | — | ส่งต่อไปยัง Controller ของ react-hook-form เพื่อตรวจสอบค่า |
locale | 'th' | 'en' | 'th' | ภาษาของป้ายและตัวเลือกใน cascade select ที่ฝังอยู่ |
texts | Partial<ThaiAddressCascadeSelectTexts> | — | ส่งต่อเป็น texts ของ cascade select ที่ฝังอยู่ |
indexLoad | 'mount' | 'visible' | 'mount' | ส่งต่อไปยังตัวโหลดดัชนีที่อยู่ของ cascade ที่ฝังอยู่ |
disabled | boolean | — | ปิดใช้งานทุก select ใน cascade select ที่ฝังอยู่ |
className | string | — | <div> ที่ครอบ cascade select และข้อความ validation |
labelClassName | string | — | ป้ายกำกับของ cascade select |
triggerClassName | string | — | trigger และช่องรหัสไปรษณีย์ของ cascade select |
popupClassName | string | — | กล่องตัวเลือกของ cascade select |
itemClassName | string | — | แต่ละตัวเลือกของ cascade select |
errorClassName | string | — | ข้อความ validation role="alert" ที่แสดงเมื่อตรวจสอบไม่ผ่าน |
คอมโพเนนต์นี้ไม่รับ value/onValueChange/ref/name (สำหรับ hidden input) แบบคอมโพเนนต์อื่น react-hook-form อ่านและเขียนค่าผ่าน control/name ที่ส่งให้แทน
ดูเพิ่มเติม
ดู การปรับแต่ง สำหรับปรับหน้าตา ถ้าฟอร์มไม่ได้ใช้ react-hook-form ดู cascade select หรือ ฟอร์มที่อยู่แบบเต็ม ซึ่งใช้รูปแบบ value/onValueChange/hidden input ตามปกติ