ข้ามไปยังเนื้อหา

react

import { useThaiAddressAutocomplete } from 'thaizip/react'

function useThaiAddressAutocomplete(options: UseThaiAddressAutocompleteOptions): {
query: string
setQuery: (value: string) => void
setQuerySilent: (value: string) => void
suggestions: ThaiAddressSuggestion[]
isOpen: boolean
selectSuggestion: (item: ThaiAddressSuggestion) => ResolvedThaiAddress | null
clear: () => void
}

Hook แบบ headless ที่ครอบ searchThaiAddress พร้อม debounce ในตัว และจัดการ state ของ query/suggestions ให้เสร็จสรรพ react/react-dom เป็น peer dependency แบบ optional และไฟล์ที่ build ออกมาแนบ "use client" มาให้แล้ว จึงใช้ได้ใน Next.js App Router โดยไม่ต้องห่อ wrapper เพิ่ม

พารามิเตอร์ (UseThaiAddressAutocompleteOptions)

หัวข้อ “พารามิเตอร์ (UseThaiAddressAutocompleteOptions)”
ชื่อชนิดค่าเริ่มต้นคำอธิบาย
indexTrigramIndex— (จำเป็น)ดัชนีที่ใช้ค้นหา ต้องไม่เป็น null/undefined ตอนเรียก hook (hook เรียกแบบมีเงื่อนไขไม่ได้ — ถ้า index โหลดแบบ async ให้แยก component ชั้นนอกที่รอ index พร้อมก่อน)
limit?number10ส่งต่อให้ searchThaiAddress ตรง ๆ — จำกัดจำนวนผลลัพธ์สำหรับ query แบบข้อความ
debounce?number200ระยะเวลารอ (มิลลิวินาที) ก่อนค้นหาจริงหลังผู้ใช้หยุดพิมพ์
threshold?number0.4ส่งต่อให้ searchThaiAddress ตรง ๆ — คะแนนขั้นต่ำที่นับว่า match
zipLimit?numberundefinedส่งต่อให้ searchThaiAddress ตรง ๆ (ไม่ได้ตั้งค่า default ในตัว hook เอง — ถ้าไม่ส่งมา searchThaiAddress จะใช้ค่าเริ่มต้นของตัวเอง คือ Infinity)
initialQuery?string''ค่าเริ่มต้นของ query ตอน render ครั้งแรก ไม่ trigger การค้นหา — เหมาะกับการเติมข้อความในช่อง input จากที่อยู่ที่เลือกไว้แล้ว (เช่น defaultValues ของ react-hook-form) โดยไม่เปิด dropdown ขึ้นมาเอง
locale?AddressLocale ('th' | 'en')undefinedภาษาของ field label ใน suggestions ที่คืนกลับ ส่งต่อให้ formatThaiAddressSuggestion
onSelect?(address: ResolvedThaiAddress) => voidundefinedเรียกหลังจาก selectSuggestion resolve ที่อยู่สำเร็จ
ชื่อชนิดคำอธิบาย
querystringข้อความในช่องค้นหาปัจจุบัน
setQuery(value: string) => voidตั้งค่า query ใหม่ — trigger การค้นหาแบบ debounce (debounce มิลลิวินาทีหลังพิมพ์ครั้งสุดท้าย)
setQuerySilent(value: string) => voidตั้งค่า query โดยไม่ค้นหาซ้ำและไม่เปิด dropdown ใหม่ — ใช้ตอนเอา label ที่เลือกแล้วมาแสดงในช่อง input หลัง selectSuggestion, หรือ seed ค่า query หลัง mount (เช่นตอน defaultValues แบบ async resolve เสร็จทีหลัง)
suggestionsThaiAddressSuggestion[]อาเรย์ผลลัพธ์จากการค้นหาล่าสุด
isOpenbooleanquery.length > 0 && suggestions.length > 0 — ใช้ตัดสินใจว่าจะแสดง dropdown หรือไม่
selectSuggestion(item: ThaiAddressSuggestion) => ResolvedThaiAddress | nullรับ suggestion (ออบเจ็กต์เต็มจาก suggestions ไม่ใช่แค่ id) แล้ว lookup record ต้นฉบับด้วย item.id แบบ O(1) ภายใน, resolve เป็น ResolvedThaiAddress, เคลียร์ suggestions, ยิง onSelect, แล้วคืนค่าที่ resolve ได้ — คืนค่า null (ไม่โยน exception) ถ้า item.id ไม่ตรงกับ suggestion ชุดล่าสุด (เช่นค้างจากผลลัพธ์เก่า) ไม่แก้ query ให้เอง — เรียก setQuerySilent/clear เองถ้าต้องการอัปเดตช่อง input ด้วย
clear() => voidรีเซ็ต query และ suggestions กลับเป็นค่าว่าง
  • index ถูกอ่านผ่าน ref ภายใน hook เอง — object reference ใหม่ของ index ที่ส่งเข้ามาในแต่ละ render จะไม่รีสตาร์ท debounce timer ที่กำลังรออยู่ แต่ถ้า index เปลี่ยนค่าจริง ๆ (เช่นสลับไปใช้ข้อมูลชุดอื่น) และมี query ค้างอยู่ hook จะค้นหาซ้ำทันทีด้วย index ใหม่โดยไม่รอ debounce
  • initialQuery และ setQuerySilent('...') (ค่าที่ไม่ว่าง) ต่างก็กัน 1 รอบการค้นหาที่จะเกิดขึ้นถัดไปไม่ให้ trigger — กันทั้ง effect debounce และ effect ที่ตอบสนองต่อ index เปลี่ยนพร้อมกัน (ทั้งสอง effect รันพร้อมกันตอน mount) ส่วน setQuerySilent('') (ค่าว่าง) จะไม่ arm การกันนี้ไว้ค้าง เพื่อไม่ให้กันคีย์สโตรกจริงถัดไปโดยไม่ตั้งใจ
  • query.length === 0 จะเคลียร์ suggestions ทันทีโดยไม่รอ debounce
import { useState } from 'react'
import { useThaiAddressAutocomplete } from 'thaizip/react'
import type { ResolvedThaiAddress, TrigramIndex } from 'thaizip'
function AddressAutocomplete({ index }: { index: TrigramIndex }) {
const [selected, setSelected] = useState<ResolvedThaiAddress | null>(null)
const { query, setQuery, setQuerySilent, suggestions, isOpen, selectSuggestion } =
useThaiAddressAutocomplete({ index, onSelect: setSelected })
return (
<div>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
{isOpen && (
<ul>
{suggestions.map((s) => (
<li
key={s.id}
onClick={() => {
const resolved = selectSuggestion(s)
if (resolved) setQuerySilent(s.label)
}}
>
{s.label} ({s.zipCode})
</li>
))}
</ul>
)}
</div>
)
}