RECIPES FOR YOUR APP
ตัวอย่างใช้งาน
ทดลองคอมโพเนนต์ แล้วคัดลอกโค้ดไปปรับให้เข้ากับฟอร์มในแอปของคุณ
ตัวอย่างใช้ React และ TypeScript ก่อนใช้งานให้ติดตั้ง thai-address-sdk ในโปรเจกต์ที่มี React อยู่แล้ว ชื่อ class ในตัวอย่างใช้สำหรับตกแต่ง สามารถเปลี่ยนให้เข้ากับแอปของคุณได้
Autocomplete ที่เลือกด้วยคีย์บอร์ดได้
หน่วงการค้นหา 200 มิลลิวินาที โหลด SDK เมื่อเริ่มพิมพ์ และส่งข้อมูลที่เลือกกลับผ่าน onSelect รองรับปุ่มขึ้น/ลง, Enter และ Escape
พิมพ์อย่างน้อย 2 ตัวอักษร ใช้ปุ่มขึ้น/ลงและ Enter เพื่อเลือก
import { ThaiAddressAutocomplete } from "./ThaiAddressAutocomplete";
<ThaiAddressAutocomplete
onSelect={(address) => {
console.log(address.province.provinceCode);
console.log(address.formattedAddress);
}}
/>;ดูและคัดลอกโค้ดคอมโพเนนต์ทั้งหมด
import { useEffect, useId, useRef, useState } from "react";
import type { SearchResult } from "thai-address-sdk";
export function ThaiAddressAutocomplete({
onSelect,
}: {
onSelect?: (address: SearchResult) => void;
}) {
const id = useId();
const [query, setQuery] = useState("");
const [results, setResults] = useState<SearchResult[]>([]);
const [open, setOpen] = useState(false);
const [active, setActive] = useState(-1);
const [selected, setSelected] = useState<SearchResult | null>(null);
const [message, setMessage] = useState("");
const request = useRef(0);
useEffect(() => {
const current = ++request.current;
if (selected || query.trim().length < 2) {
setResults([]);
setMessage("");
return;
}
setMessage("กำลังค้นหา…");
const timer = setTimeout(async () => {
try {
const { search } = await import("thai-address-sdk");
if (current !== request.current) return;
const matches = search(query.trim(), { limit: 6 });
setResults(matches);
setMessage(
matches.length
? `พบ ${matches.length} รายการ`
: "ไม่พบพื้นที่ ลองใช้ชื่อจังหวัด",
);
} catch {
if (current === request.current)
setMessage("โหลดข้อมูลไม่สำเร็จ ลองพิมพ์ค้นหาใหม่");
}
}, 200);
return () => {
++request.current;
clearTimeout(timer);
};
}, [query, selected]);
function choose(address: SearchResult) {
++request.current;
setSelected(address);
setQuery(address.formattedAddress);
setOpen(false);
setActive(-1);
onSelect?.(address);
}
const visible = open && results.length > 0;
return (
<div className="autocomplete-example">
<label className="field-label" htmlFor={`${id}-input`}>
ค้นหาที่อยู่ไทย
</label>
<input
id={`${id}-input`}
className="field-input"
role="combobox"
aria-autocomplete="list"
aria-expanded={visible}
aria-controls={visible ? `${id}-list` : undefined}
aria-activedescendant={
visible && active >= 0 ? `${id}-option-${active}` : undefined
}
aria-describedby={`${id}-hint`}
value={query}
placeholder="เช่น อยูทยา หรือ สุเทพ"
autoComplete="off"
maxLength={160}
onChange={(event) => {
++request.current;
setQuery(event.target.value);
setSelected(null);
setResults([]);
setActive(-1);
setOpen(true);
}}
onFocus={() => setOpen(true)}
onBlur={() => setOpen(false)}
onKeyDown={(event) => {
if (event.key === "Escape") setOpen(false);
if (
(event.key === "ArrowDown" || event.key === "ArrowUp") &&
results.length
) {
event.preventDefault();
setOpen(true);
setActive((index) =>
event.key === "ArrowDown"
? (index + 1) % results.length
: index <= 0
? results.length - 1
: index - 1,
);
}
if (event.key === "Enter" && visible && active >= 0) {
event.preventDefault();
choose(results[active]);
}
}}
/>
<p id={`${id}-hint`} className="small-note">
พิมพ์อย่างน้อย 2 ตัวอักษร ใช้ปุ่มขึ้น/ลงและ Enter เพื่อเลือก
</p>
{visible && (
<ul
id={`${id}-list`}
role="listbox"
aria-label="ที่อยู่ที่แนะนำ"
className="autocomplete-options"
>
{results.map((address, index) => (
<li
key={`${address.type}-${address.formattedAddress}`}
id={`${id}-option-${index}`}
role="option"
aria-selected={index === active}
onMouseDown={(event) => event.preventDefault()}
onClick={() => choose(address)}
>
{address.formattedAddress}
</li>
))}
</ul>
)}
<p role="status" className="small-note">
{selected ? `เลือกแล้ว: ${selected.formattedAddress}` : message}
</p>
</div>
);
}จังหวัด อำเภอ ตำบล ที่สัมพันธ์กัน
ตัวอย่างนี้ล้างค่าระดับล่างเมื่อเปลี่ยนจังหวัดหรืออำเภอ และอ่านรหัสไปรษณีย์จากตำบลที่เลือก เก็บ state ด้วยรหัสพื้นที่ที่เป็นตัวเลข
ดูและคัดลอกโค้ดคอมโพเนนต์ทั้งหมด
import { useId, useState } from "react";
import { getProvinces, getDistricts, getSubdistricts } from "thai-address-sdk";
export function ThaiAddressDropdown() {
const id = useId();
const [provinceCode, setProvinceCode] = useState<number>();
const [districtCode, setDistrictCode] = useState<number>();
const [subdistrictCode, setSubdistrictCode] = useState<number>();
const provinces = getProvinces();
const districts = provinceCode ? getDistricts({ provinceCode }) : [];
const subdistricts = districtCode ? getSubdistricts({ districtCode }) : [];
const selected = subdistricts.find(
(item) => item.subdistrictCode === subdistrictCode,
);
return (
<div className="dropdown-example">
<label className="field-label" htmlFor={`${id}-province`}>
จังหวัด
</label>
<select
className="field-input"
id={`${id}-province`}
value={provinceCode ?? ""}
onChange={(event) => {
setProvinceCode(
event.target.value ? Number(event.target.value) : undefined,
);
setDistrictCode(undefined);
setSubdistrictCode(undefined);
}}
>
<option value="">เลือกจังหวัด</option>
{provinces.map((item) => (
<option key={item.provinceCode} value={item.provinceCode}>
{item.provinceNameTh}
</option>
))}
</select>
<label className="field-label" htmlFor={`${id}-district`}>
อำเภอ/เขต
</label>
<select
className="field-input"
id={`${id}-district`}
value={districtCode ?? ""}
disabled={!provinceCode}
onChange={(event) => {
setDistrictCode(
event.target.value ? Number(event.target.value) : undefined,
);
setSubdistrictCode(undefined);
}}
>
<option value="">เลือกอำเภอ/เขต</option>
{districts.map((item) => (
<option key={item.districtCode} value={item.districtCode}>
{item.districtNameTh}
</option>
))}
</select>
<label className="field-label" htmlFor={`${id}-subdistrict`}>
ตำบล/แขวง
</label>
<select
className="field-input"
id={`${id}-subdistrict`}
value={subdistrictCode ?? ""}
disabled={!districtCode}
onChange={(event) =>
setSubdistrictCode(
event.target.value ? Number(event.target.value) : undefined,
)
}
>
<option value="">เลือกตำบล/แขวง</option>
{subdistricts.map((item) => (
<option key={item.subdistrictCode} value={item.subdistrictCode}>
{item.subdistrictNameTh}
</option>
))}
</select>
<output className="dropdown-postcode">
{selected
? `รหัสไปรษณีย์ ${selected.postalCode}`
: "เลือกตำบลเพื่อแสดงรหัสไปรษณีย์"}
</output>
</div>
);
}ตัวอย่าง dropdown import ข้อมูลโดยตรง หากฟอร์มอยู่ท้ายหน้าหรือเปิดใน dialog สามารถโหลดคอมโพเนนต์นี้ภายหลังเพื่อลดงานตอนเปิดหน้าแรก
โหลด SDK เมื่อผู้ใช้เริ่มค้นหา
ใส่ "use client" ในไฟล์คอมโพเนนต์ แล้วใช้ dynamic import ภายใน event handler ตัวอย่างนี้มีสถานะกำลังค้นหาและข้อความเมื่อโหลดข้อมูลไม่สำเร็จ
"use client";
// ภายใน event handler ของ Client Component
const { search } = await import("thai-address-sdk");
const results = search("เชียงใหม่", { limit: 5 });ดูและคัดลอก Client Component ทั้งหมด
"use client";
import { useId, useState, type SyntheticEvent } from "react";
import type { SearchResult } from "thai-address-sdk";
export default function NextAddressSearch() {
const id = useId();
const [query, setQuery] = useState("");
const [results, setResults] = useState<SearchResult[]>([]);
const [busy, setBusy] = useState(false);
const [message, setMessage] = useState("");
async function handleSearch(event: SyntheticEvent<HTMLFormElement>) {
event.preventDefault();
if (busy || query.trim().length < 2) return;
setBusy(true);
setMessage("กำลังค้นหา…");
setResults([]);
try {
// โหลด SDK เมื่อค้นหาครั้งแรก ฟังก์ชัน search ไม่ต้อง await
const { search } = await import("thai-address-sdk");
const found = search(query.trim(), { limit: 5 });
setResults(found);
setMessage(
found.length
? `พบ ${found.length} รายการ`
: "ไม่พบพื้นที่ ลองใช้ชื่อจังหวัด",
);
} catch {
setMessage("โหลดข้อมูลไม่สำเร็จ กรุณาลองค้นหาอีกครั้ง");
} finally {
setBusy(false);
}
}
return (
<form onSubmit={handleSearch}>
<label htmlFor={id}>ค้นหาที่อยู่ไทย</label>
<input
id={id}
value={query}
onChange={(event) => setQuery(event.target.value)}
minLength={2}
maxLength={160}
required
disabled={busy}
/>
<button type="submit" disabled={busy}>
{busy ? "กำลังค้นหา…" : "ค้นหา"}
</button>
<p role="status">{message}</p>
<ul>
{results.map((result) => (
<li key={`${result.type}-${result.formattedAddress}`}>
{result.formattedAddress}
</li>
))}
</ul>
</form>
);
}จากนั้น import NextAddressSearch ไปวางในหน้า App Router ได้ตามปกติ การ await ใช้สำหรับโหลดโมดูล ส่วน search() ทำงานทันทีบนข้อมูลในแพ็กเกจ
จัดการผลลัพธ์ก่อนนำไปบันทึก
เมื่อใช้ normalization ให้นำสถานะมาเลือกหน้าตาการตอบกลับ ฟังก์ชัน setMessage และ setChoices ในโค้ดด้านล่างเป็นตัวอย่าง callback ของ UI ที่แอปต้องกำหนดเอง
import { normalizeAddress } from "thai-address-sdk";
import type { AddressHierarchy } from "thai-address-sdk";
function reviewAddress(
input: string,
setMessage: (message: string) => void,
setChoices: (choices: AddressHierarchy[]) => void,
) {
const result = normalizeAddress(input);
setChoices([]);
switch (result.status) {
case "matched":
setMessage("ตรวจสอบที่อยู่ก่อนยืนยัน");
if (result.bestMatch) setChoices([result.bestMatch]);
break;
case "ambiguous":
setMessage("กรุณาเลือกพื้นที่ที่ต้องการ");
setChoices(result.bestMatch
? [result.bestMatch, ...result.alternatives]
: result.alternatives);
break;
case "partial":
setMessage("เพิ่มชื่ออำเภอหรือจังหวัดให้ครบขึ้น");
break;
case "not_found":
setMessage("ไม่พบพื้นที่ ลองตรวจสอบข้อความอีกครั้ง");
}
}