react-thaizip

ฟิลด์ที่อยู่สำหรับ 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

จังหวัด
อำเภอ/เขต
ตำบล/แขวง
รหัสไปรษณีย์

ค่าที่ส่ง

null
'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-field

CLI เลือกไฟล์ให้ตาม 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 ที่ฝังอยู่เป็นภาษาอังกฤษ

Province
District
Sub-district
Postal code

Submitted value

null
'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

จังหวัด
อำเภอ/เขต
ตำบล/แขวง
รหัสไปรษณีย์

ค่าที่ส่ง

null
'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 ได้ค่าทั้งหมดพร้อมกัน

จังหวัด
อำเภอ/เขต
ตำบล/แขวง
รหัสไปรษณีย์

ค่าที่ส่ง

null
'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

PropTypeค่าเริ่มต้นรายละเอียด
controlControl<TFieldValues>—จำเป็น ค่า control จาก useForm() ของ react-hook-form
nameFieldPath<TFieldValues>—จำเป็น path ของฟิลด์ในฟอร์ม ชนิดค่าต้องเป็น ResolvedThaiAddress | null
rulesOmit<RegisterOptions<…>, 'valueAsNumber' | 'valueAsDate' | 'setValueAs' | 'disabled'>—ส่งต่อไปยัง Controller ของ react-hook-form เพื่อตรวจสอบค่า
locale'th' | 'en''th'ภาษาของป้ายและตัวเลือกใน cascade select ที่ฝังอยู่
textsPartial<ThaiAddressCascadeSelectTexts>—ส่งต่อเป็น texts ของ cascade select ที่ฝังอยู่
indexLoad'mount' | 'visible''mount'ส่งต่อไปยังตัวโหลดดัชนีที่อยู่ของ cascade ที่ฝังอยู่
disabledboolean—ปิดใช้งานทุก select ใน cascade select ที่ฝังอยู่
classNamestring—<div> ที่ครอบ cascade select และข้อความ validation
labelClassNamestring—ป้ายกำกับของ cascade select
triggerClassNamestring—trigger และช่องรหัสไปรษณีย์ของ cascade select
popupClassNamestring—กล่องตัวเลือกของ cascade select
itemClassNamestring—แต่ละตัวเลือกของ cascade select
errorClassNamestring—ข้อความ validation role="alert" ที่แสดงเมื่อตรวจสอบไม่ผ่าน

คอมโพเนนต์นี้ไม่รับ value/onValueChange/ref/name (สำหรับ hidden input) แบบคอมโพเนนต์อื่น react-hook-form อ่านและเขียนค่าผ่าน control/name ที่ส่งให้แทน

ดูเพิ่มเติม

ดู การปรับแต่ง สำหรับปรับหน้าตา ถ้าฟอร์มไม่ได้ใช้ react-hook-form ดู cascade select หรือ ฟอร์มที่อยู่แบบเต็ม ซึ่งใช้รูปแบบ value/onValueChange/hidden input ตามปกติ

On this page