RECIPES FOR YOUR APP

ตัวอย่างใช้งาน

ทดลองคอมโพเนนต์ แล้วคัดลอกโค้ดไปปรับให้เข้ากับฟอร์มในแอปของคุณ

ตัวอย่างใช้ React และ TypeScript ก่อนใช้งานให้ติดตั้ง thai-address-sdk ในโปรเจกต์ที่มี React อยู่แล้ว ชื่อ class ในตัวอย่างใช้สำหรับตกแต่ง สามารถเปลี่ยนให้เข้ากับแอปของคุณได้

REACT · LIVE EXAMPLE

Autocomplete ที่เลือกด้วยคีย์บอร์ดได้

หน่วงการค้นหา 200 มิลลิวินาที โหลด SDK เมื่อเริ่มพิมพ์ และส่งข้อมูลที่เลือกกลับผ่าน onSelect รองรับปุ่มขึ้น/ลง, Enter และ Escape

พิมพ์อย่างน้อย 2 ตัวอักษร ใช้ปุ่มขึ้น/ลงและ Enter เพื่อเลือก

การเรียกใช้
import { ThaiAddressAutocomplete } from "./ThaiAddressAutocomplete";

<ThaiAddressAutocomplete
  onSelect={(address) => {
    console.log(address.province.provinceCode);
    console.log(address.formattedAddress);
  }}
/>;
ดูและคัดลอกโค้ดคอมโพเนนต์ทั้งหมด
ThaiAddressAutocomplete.tsx
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>
  );
}
NEXT.JS · APP ROUTER

โหลด 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 ทั้งหมด
NextAddressSearch.tsx
"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 ที่แอปต้องกำหนดเอง

TypeScript
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("ไม่พบพื้นที่ ลองตรวจสอบข้อความอีกครั้ง");
  }
}
อ่านรายละเอียด NormalizeResult