UI libraries
Prop getters fill a plain select in one line and work out the disabled state from the chain, with flavours for shadcn/ui, Ant Design and your own component.
Prop getters
getProvinceProps(), getDistrictProps() and getNeighbourhoodProps() fill a plain <select> in one line:
const {
getProvinceProps,
getDistrictProps,
getNeighbourhoodProps,
getPostalCodeProps,
} = useViraAddress()
<select {...getProvinceProps()} />
<select {...getDistrictProps()} />
<select {...getNeighbourhoodProps()} />
<input {...getPostalCodeProps()} />The options are rendered for you; the empty first option is the placeholder and lets the user clear the level again. The getters also set value, onChange, disabled, aria-busy, name and id. The hidden postal code input carries the value into a plain form submit.
disabled follows the chain: the district waits for a province, and the neighbourhood waits for a district and its data. Override anything through the options, or by setting the prop after the spread:
<select {...getProvinceProps({ placeholder: "Select a province" })} />
<select {...getDistrictProps()} disabled={false} />
<select {...getNeighbourhoodProps({ name: "area", id: "area" })} />The default placeholders are in Turkish, because the people filling in the form are in Türkiye. Pass placeholder to use your own wording. For other components, pick a flavour with as: headless, shadcn or antd.
headless
For your own select component: an options list plus onChange(value).
<MySelect {...getProvinceProps({ as: "headless" })} />
<MySelect {...getDistrictProps({ as: "headless" })} />
<MySelect {...getNeighbourhoodProps({ as: "headless" })} />| Prop | Value |
|---|---|
options | A { value, label } list |
value | The selected value; "" when nothing is picked |
onChange | Takes a bare value or a change event |
disabled | Worked out from the chain |
loading, aria-busy | true on the neighbourhood level while it loads |
name, id | From the names option or the level name; id comes from useId |
placeholder | "İl seçiniz", "İlçe seçiniz", "Mahalle seçiniz" (Turkish for "select a province" and so on) |
shadcn/ui
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
const { provinces, getProvinceProps } = useViraAddress()
<Select {...getProvinceProps({ as: "shadcn" })} items={provinces}>
<SelectTrigger>
<SelectValue placeholder="Select a province" />
</SelectTrigger>
<SelectContent>
{provinces.map((p) => (
<SelectItem key={p.value} value={p.value}>
{p.label}
</SelectItem>
))}
</SelectContent>
</Select>For shadcn/ui, value, onValueChange and disabled go on Select; the items are yours. An empty value shows the SelectValue placeholder. items is needed on the Base UI version of shadcn/ui to show the selected label, and is harmless on the Radix version.
Ant Design
import { Select } from "antd"
<Select {...getProvinceProps({ as: "antd" })} />
<Select {...getDistrictProps({ as: "antd" })} />
<Select {...getNeighbourhoodProps({ as: "antd" })} />With Ant Design, options, loading and search come ready. Ant Design's own filter lowercases İ into i̇, so a search for istanbul would miss İstanbul; this flavour passes a Turkish-aware filterOption instead. An empty value is sent as undefined, so the placeholder shows.
Lists and setters
If your component's props match none of the flavours, use the lists and setters directly. MUI Autocomplete:
import Autocomplete from "@mui/material/Autocomplete"
import TextField from "@mui/material/TextField"
const { provinces, setProvince, value } = useViraAddress()
<Autocomplete
options={provinces}
value={provinces.find((p) => p.value === value.provinceCode) ?? null}
onChange={(_, option) => setProvince(option?.value ?? null)}
renderInput={(params) => <TextField {...params} label="Province" />}
/>Writing your own search? toSearchKey ignores case and Turkish characters:
import { toSearchKey } from "@virastack/address"
// a search for "kadikoy" finds "Kadıköy"
const hits = neighbourhoods.filter((n) =>
toSearchKey(n.label).includes(toSearchKey(query)),
)Long lists
A district can hold several hundred neighbourhoods and villages; the largest, Artvin's Yusufeli district, has 674. Use a searchable select there, and virtualise the list if it renders thousands of rows.
