# ค้นหาที่อยู่แบบ autocomplete

> ค้นหาที่อยู่ไทยจากข้อความที่พิมพ์ พร้อมค่าที่อยู่แบบมีโครงสร้าง

Source: https://react-thai-zip.vercel.app/docs/components/autocomplete

`ThaiAddressAutocomplete` เหมาะกับหน้าฟอร์มที่ผู้ใช้ต้องการค้นหาที่อยู่ด้วยตำบล อำเภอ/เขต จังหวัด หรือรหัสไปรษณีย์

```tsx title="autocomplete-demo.tsx"
'use client'

import { ThaiAddressAutocomplete } from '@/components/thai-address-autocomplete'

export default function AutocompleteDemo() {
  return <ThaiAddressAutocomplete onValueChange={(address) => console.log(address)} />
}
```

## ติดตั้ง

**CLI**

```bash
npx react-thaizip add autocomplete
```

CLI เลือกไฟล์ให้ตาม style ที่ `init` บันทึกไว้: โปรเจกต์ที่มี `components.json` ของ shadcn/ui จะได้ไฟล์ที่ประกอบจาก primitives ของ shadcn (Base UI, Radix หรือ React Aria) นอกนั้นได้แบบ vanilla

**ติดตั้งเอง — Vanilla**

1. ติดตั้ง dependencies: `npm install clsx tailwind-merge thaizip @base-ui/react`
1. คัดลอกไฟล์ต่อไปนี้ลงในโปรเจกต์:
   - `lib/utils.ts` — [ซอร์ส](https://github.com/naay99999/react-thai-zip/blob/main/templates/react/ts/lib/utils.ts)
   - `hooks/use-thai-address-index.ts` — [ซอร์ส](https://github.com/naay99999/react-thai-zip/blob/main/templates/react/ts/hooks/use-thai-address-index.ts)
   - `components/thai-address-autocomplete.tsx` — [ซอร์ส](https://github.com/naay99999/react-thai-zip/blob/main/templates/react/ts/thai-address-autocomplete.tsx)
1. แก้ import `@/lib/...` และ `@/hooks/...` ให้ตรงกับตำแหน่งไฟล์ในโปรเจกต์ (CLI แก้ให้อัตโนมัติ)
1. แบบ vanilla ต้องมี design tokens ใน global stylesheet ด้วย ดู [การปรับแต่ง](https://react-thai-zip.vercel.app/docs/guides/customization#tokens-ที่-init-เพิ่ม)

**ติดตั้งเอง — shadcn/ui · Base UI**

1. ติดตั้ง dependencies: `npm install clsx tailwind-merge thaizip`
1. เพิ่ม shadcn/ui primitives ที่คอมโพเนนต์ใช้: `npx shadcn@latest add popover command button`
1. คัดลอกไฟล์ต่อไปนี้ลงในโปรเจกต์:
   - `lib/utils.ts` — [ซอร์ส](https://github.com/naay99999/react-thai-zip/blob/main/templates/react/ts/lib/utils.ts)
   - `hooks/use-thai-address-index.ts` — [ซอร์ส](https://github.com/naay99999/react-thai-zip/blob/main/templates/react/ts/hooks/use-thai-address-index.ts)
   - `components/thai-address-autocomplete.tsx` — [ซอร์ส](https://github.com/naay99999/react-thai-zip/blob/main/templates/react/ts/shadcn/base/thai-address-autocomplete.tsx)
1. แก้ import `@/lib/...` และ `@/hooks/...` ให้ตรงกับตำแหน่งไฟล์ในโปรเจกต์ (CLI แก้ให้อัตโนมัติ)

**ติดตั้งเอง — shadcn/ui · Radix**

1. ติดตั้ง dependencies: `npm install clsx tailwind-merge thaizip`
1. เพิ่ม shadcn/ui primitives ที่คอมโพเนนต์ใช้: `npx shadcn@latest add popover command button`
1. คัดลอกไฟล์ต่อไปนี้ลงในโปรเจกต์:
   - `lib/utils.ts` — [ซอร์ส](https://github.com/naay99999/react-thai-zip/blob/main/templates/react/ts/lib/utils.ts)
   - `hooks/use-thai-address-index.ts` — [ซอร์ส](https://github.com/naay99999/react-thai-zip/blob/main/templates/react/ts/hooks/use-thai-address-index.ts)
   - `components/thai-address-autocomplete.tsx` — [ซอร์ส](https://github.com/naay99999/react-thai-zip/blob/main/templates/react/ts/shadcn/radix/thai-address-autocomplete.tsx)
1. แก้ import `@/lib/...` และ `@/hooks/...` ให้ตรงกับตำแหน่งไฟล์ในโปรเจกต์ (CLI แก้ให้อัตโนมัติ)

**ติดตั้งเอง — shadcn/ui · React Aria**

1. ติดตั้ง dependencies: `npm install clsx tailwind-merge thaizip`
1. เพิ่ม shadcn/ui primitives ที่คอมโพเนนต์ใช้: `npx shadcn@latest add popover command button`
1. คัดลอกไฟล์ต่อไปนี้ลงในโปรเจกต์:
   - `lib/utils.ts` — [ซอร์ส](https://github.com/naay99999/react-thai-zip/blob/main/templates/react/ts/lib/utils.ts)
   - `hooks/use-thai-address-index.ts` — [ซอร์ส](https://github.com/naay99999/react-thai-zip/blob/main/templates/react/ts/hooks/use-thai-address-index.ts)
   - `components/thai-address-autocomplete.tsx` — [ซอร์ส](https://github.com/naay99999/react-thai-zip/blob/main/templates/react/ts/shadcn/aria/thai-address-autocomplete.tsx)
1. แก้ import `@/lib/...` และ `@/hooks/...` ให้ตรงกับตำแหน่งไฟล์ในโปรเจกต์ (CLI แก้ให้อัตโนมัติ)

## วิธีใช้

```tsx
import { ThaiAddressAutocomplete } from '@/components/thai-address-autocomplete'
```

```tsx
<ThaiAddressAutocomplete onValueChange={(address) => console.log(address)} />
```

ค่าใน `onValueChange` คือ `ResolvedThaiAddress | null` เมื่อผู้ใช้เลือกคำแนะนำจะได้ที่อยู่แบบมีโครงสร้าง และการพิมพ์ให้ข้อความว่างจะล้างที่อยู่ที่เลือกอยู่แล้วด้วยค่า `null`

## ตัวอย่าง

### ค้นหาภาษาอังกฤษ

ใช้ `locale="en"` เพื่อแสดงป้ายกำกับ คำใบ้ และผลลัพธ์เป็นภาษาอังกฤษ ข้อมูลที่ส่งกลับยังมีชื่อทั้งไทยและอังกฤษ

```tsx title="autocomplete-en.tsx"
'use client'

import { ThaiAddressAutocomplete } from '@/components/thai-address-autocomplete'

export default function AutocompleteEn() {
  return <ThaiAddressAutocomplete locale="en" onValueChange={(address) => console.log(address)} />
}
```

### ป้ายกำกับ

คอมโพเนนต์ไม่มีป้ายกำกับในตัว ครอบด้วย `<label>` เพื่อให้ช่องมีชื่อที่ screen reader อ่านได้ และเพิ่มคำอธิบายไว้ด้านล่าง

```tsx title="autocomplete-label.tsx"
import { ThaiAddressAutocomplete } from '@/components/thai-address-autocomplete'

export default function AutocompleteLabel() {
  return (
    <div className="flex flex-col gap-2">
      <label className="flex flex-col gap-2 text-sm font-medium">
        ที่อยู่จัดส่ง
        <ThaiAddressAutocomplete />
      </label>
      <p className="text-sm text-muted-foreground">พิมพ์ตำบล อำเภอ หรือรหัสไปรษณีย์ แล้วเลือกจากรายการ</p>
    </div>
  )
}
```

### ควบคุมค่าจากภายนอก

ส่ง `value` เพื่อควบคุมค่า หรือส่ง `defaultValue` เพื่อตั้งค่าเริ่มต้นสำหรับโหมด uncontrolled ปุ่มในตัวอย่างนี้ล้างค่าที่ควบคุมอยู่และแสดงผลลัพธ์ที่ callback ส่งกลับมา

```tsx title="autocomplete-controlled.tsx"
'use client'

import { useState } from 'react'
import type { ResolvedThaiAddress } from 'thaizip'
import { ThaiAddressAutocomplete } from '@/components/thai-address-autocomplete'

export default function AutocompleteControlled() {
  const [address, setAddress] = useState<ResolvedThaiAddress | null>(null)

  return (
    <div className="flex flex-col gap-3">
      <ThaiAddressAutocomplete value={address} onValueChange={setAddress} />
      <div>
        <button type="button" className="rounded-md border border-input bg-background px-3 py-1.5 text-sm hover:bg-accent" onClick={() => setAddress(null)}>
          ล้างค่า
        </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(address, null, 2)}</pre>
    </div>
  )
}
```

### ปิดใช้งาน

ส่ง `disabled` เพื่อปิดช่องค้นหา

```tsx title="autocomplete-disabled.tsx"
'use client'

import { ThaiAddressAutocomplete } from '@/components/thai-address-autocomplete'

export default function AutocompleteDisabled() {
  return <ThaiAddressAutocomplete disabled />
}
```

### ไม่ผ่านการตรวจสอบ

ส่ง `aria-invalid` เพื่อแสดงว่าช่องนี้ไม่ผ่านการตรวจสอบ เช่นหลังตรวจฟอร์มไม่ผ่าน

```tsx title="autocomplete-invalid.tsx"
'use client'

import { ThaiAddressAutocomplete } from '@/components/thai-address-autocomplete'

export default function AutocompleteInvalid() {
  return <ThaiAddressAutocomplete aria-invalid />
}
```

### ปรับข้อความ

ใช้ `texts` เพื่อแทนที่ placeholder และข้อความตอนโหลด/ผิดพลาด/ไม่พบผลลัพธ์ คีย์ที่ไม่ได้ส่งจะใช้ค่าเริ่มต้นตาม `locale`

```tsx title="autocomplete-texts.tsx"
import { ThaiAddressAutocomplete } from '@/components/thai-address-autocomplete'

export default function AutocompleteTexts() {
  return (
    <ThaiAddressAutocomplete
      texts={{
        placeholder: 'ค้นหาจากรหัสไปรษณีย์ เช่น 10110',
        emptyText: 'ไม่พบที่อยู่นี้ ลองพิมพ์ชื่ออำเภอแทน',
      }}
    />
  )
}
```

### ปรับสไตล์

แต่ละส่วนของคอมโพเนนต์มี class prop ของตัวเอง (ดู [API Reference](#api-reference)) class ที่ส่งจะรวมด้วย `cn` จึงทับค่าเริ่มต้นได้

```tsx title="autocomplete-styled.tsx"
import { ThaiAddressAutocomplete } from '@/components/thai-address-autocomplete'

export default function AutocompleteStyled() {
  return (
    <ThaiAddressAutocomplete
      inputClassName="rounded-full"
      popupClassName="rounded-2xl"
      itemClassName="rounded-xl"
    />
  )
}
```

### ใช้ในฟอร์ม

ส่ง `name` เพื่อส่งที่อยู่ที่เลือกไปกับฟอร์มปกติ คอมโพเนนต์จะสร้าง hidden inputs สี่ช่อง (`address-subdistrict`, `address-district`, `address-province`, `address-zipcode`) และ `required` จะกันไม่ให้ส่งฟอร์มจนกว่าจะเลือกที่อยู่ ดูรูปแบบอื่นได้ที่ [การใช้งานร่วมกับฟอร์ม](https://react-thai-zip.vercel.app/docs/guides/forms)

```tsx title="form-autocomplete.tsx"
'use client'

import { useRef, useState, type FormEvent } from 'react'
import { ThaiAddressAutocomplete } from '@/components/thai-address-autocomplete'

export default function FormAutocomplete() {
  const ref = useRef<HTMLInputElement>(null)
  const [payload, setPayload] = useState<Record<string, FormDataEntryValue> | null>(null)

  function handleSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()
    setPayload(Object.fromEntries(new FormData(event.currentTarget)))
  }

  return (
    <form className="flex flex-col gap-3" onSubmit={handleSubmit}>
      <ThaiAddressAutocomplete
        ref={ref}
        name="address"
        required
      />
      <div className="flex gap-2">
        <button type="button" className="rounded-md border border-input bg-background px-3 py-1.5 text-sm hover:bg-accent" onClick={() => ref.current?.focus()}>
          โฟกัสช่องที่อยู่
        </button>
        <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">FormData ที่ส่ง</p>
      <pre className="max-h-72 overflow-auto rounded-md border bg-muted p-3 text-xs">{JSON.stringify(payload, null, 2)}</pre>
    </form>
  )
}
```

## API Reference

| Prop | Type | ค่าเริ่มต้น | รายละเอียด |
| --- | --- | --- | --- |
| `value` | `ResolvedThaiAddress \| null` | — | ค่าที่ควบคุมอยู่; ส่ง `null` เพื่อล้าง |
| `defaultValue` | `ResolvedThaiAddress \| null` | — | ค่าเริ่มต้นของโหมด uncontrolled และเติมข้อความค้นหาเริ่มต้น |
| `onValueChange` | `(address: ResolvedThaiAddress \| null) => void` | — | เรียกเมื่อเลือกหรือล้างที่อยู่ |
| `name` | `string` | — | สร้าง hidden inputs สี่ช่อง: `<name>-subdistrict`, `-district`, `-province`, `-zipcode` |
| `locale` | `'th' \| 'en'` | `'th'` | ภาษาของผลลัพธ์และข้อความเริ่มต้น |
| `texts` | `Partial<ThaiAddressAutocompleteTexts>` | — | แทนที่ `placeholder`, `clearAriaLabel`, `loadingText`, `errorText`, `retryLabel`, `emptyText` |
| `indexLoad` | `'mount' \| 'visible'` | `'mount'` | โหลดข้อมูลที่อยู่ตอน mount หรือรอจนช่องเลื่อนเข้ามาในจอ |
| `limit` | `number` | `10` | จำนวนคำแนะนำสูงสุด |
| `debounce` | `number` | `200` | ระยะหน่วงก่อนค้นหา (มิลลิวินาที) |
| `threshold` | `number` | `0.4` | คะแนนความใกล้เคียงขั้นต่ำ (0–1) ที่คำแนะนำจะถูกแสดง |
| `disabled` | `boolean` | `false` | ปิดใช้งานช่องค้นหา |
| `required` | `boolean` | — | กำหนดให้ช่องต้องมีค่า |
| `aria-invalid` | `React.AriaAttributes['aria-invalid']` | — | ระบุสถานะไม่ผ่านการตรวจสอบ |
| `onBlur` | `React.FocusEventHandler` | — | Vanilla: ทำงานเมื่อ input สูญเสียโฟกัส ส่วน shadcn/ui: ทำงานที่ปุ่ม trigger (`HTMLButtonElement`) |
| `onError` | `(error: Error) => void` | — | ทำงานเมื่อโหลดดัชนีที่อยู่ไม่สำเร็จ |
| `className` | `string` | — | wrapper หลัก |
| `inputClassName` | `string` | — | ช่องพิมพ์ค้นหา |
| `popupClassName` | `string` | — | กล่องผลลัพธ์ |
| `itemClassName` | `string` | — | แต่ละผลลัพธ์ |
| `ref` | `React.Ref<HTMLInputElement>` | — | ส่งต่อไปยัง input ใน style shadcn/ui นั้น input อยู่ใน popover จึงเป็น `null` ระหว่างที่ popover ปิดอยู่ |

`limit`, `debounce` และ `threshold` ส่งต่อตรงไปยัง hook `useThaiAddressAutocomplete` ของ thaizip

## ดูเพิ่มเติม

ดู [การใช้งานร่วมกับฟอร์ม](https://react-thai-zip.vercel.app/docs/guides/forms) สำหรับการส่งค่าที่อยู่ และ [การปรับแต่ง](https://react-thai-zip.vercel.app/docs/guides/customization) สำหรับปรับหน้าตา ส่วน UI แบบเลือกตามลำดับ ดู [cascade select](https://react-thai-zip.vercel.app/docs/components/cascade-select)
