{
  "aliases": [
    "cascading-selects",
    "country-state-city",
    "chained-selects",
    "linked-selects"
  ],
  "code": "// Live page: /showcase/form-dependent-selects (docs/showcase/form-dependent-selects.tsx).\n// Three rules: (1) child disabled until parent has a value + helper saying why;\n// (2) child loading while its options fetch; (3) parent change resets EVERY descendant to \"\".\n// Reset to \"\", Select's controlled empty value — undefined flips it to uncontrolled.\nimport * as React from \"react\";\nimport { Form, FormField, Select } from \"@godxjp/ui/data-entry\";\nimport { Flex } from \"@godxjp/ui/layout\";\nimport { useTranslation } from \"@godxjp/ui/i18n\";\n\ntype Option = { value: string; label: string };\ntype Address = { country: string; region: string; city: string };\n\n// Fetch options for a parent value. The answer is stored WITH the parent it answers, so\n// `loading` is derived and a slow answer for an OLD parent is never shown on the new one.\nfunction useChildOptions(parent: string, fetchOptions: (parent: string) => Promise<Option[]>) {\n  const [answer, setAnswer] = React.useState<{ parent: string; options: Option[] }>({ parent: \"\", options: [] });\n  React.useEffect(() => {\n    if (!parent) return;\n    let live = true;\n    void fetchOptions(parent).then((options) => { if (live) setAnswer({ parent, options }); });\n    return () => { live = false; };\n  }, [parent, fetchOptions]);\n  const ready = parent !== \"\" && answer.parent === parent;\n  return { options: ready ? answer.options : [], loading: parent !== \"\" && !ready };\n}\n\nexport function AddressFields({\n  fetchRegions,\n  fetchCities,\n}: {\n  fetchRegions: (country: string) => Promise<Option[]>; // stable (module-level or useCallback)\n  fetchCities: (region: string) => Promise<Option[]>;\n}) {\n  const { t, locale } = useTranslation();\n  const [value, setValue] = React.useState<Address>({ country: \"\", region: \"\", city: \"\" });\n  const regions = useChildOptions(value.country, fetchRegions);\n  const cities = useChildOptions(value.region, fetchCities);\n\n  // ISO 3166-1 alpha-2 → the reader's language, no catalogue needed.\n  const countries = React.useMemo(() => {\n    const names = new Intl.DisplayNames([locale], { type: \"region\" });\n    return [\"JP\", \"US\", \"VN\"].map((code) => ({ value: code, label: names.of(code) ?? code }));\n  }, [locale]);\n\n  return (\n    <Form controlWidth=\"20rem\">\n      <Flex direction=\"col\" gap=\"md\">\n        <FormField id=\"country\" label={t(\"address.country\")}>\n          <Select\n            value={value.country}\n            onValueChange={(country: string) => setValue({ country, region: \"\", city: \"\" })}\n            options={countries}\n            placeholder={t(\"address.countryPlaceholder\")}\n          />\n        </FormField>\n        <FormField\n          id=\"region\"\n          label={t(\"address.region\")}\n          helper={value.country ? undefined : t(\"address.selectCountryFirst\")}\n        >\n          <Select\n            value={value.region}\n            onValueChange={(region: string) => setValue({ ...value, region, city: \"\" })}\n            options={regions.options}\n            disabled={!value.country}\n            loading={regions.loading}\n            placeholder={t(\"address.regionPlaceholder\")}\n          />\n        </FormField>\n        <FormField\n          id=\"city\"\n          label={t(\"address.city\")}\n          helper={value.region ? undefined : t(\"address.selectRegionFirst\")}\n        >\n          <Select\n            value={value.city}\n            onValueChange={(city: string) => setValue({ ...value, city })}\n            options={cities.options}\n            disabled={!value.region}\n            loading={cities.loading}\n            placeholder={t(\"address.cityPlaceholder\")}\n          />\n        </FormField>\n      </Flex>\n    </Form>\n  );\n}",
  "name": "dependent-selects",
  "tagline": "Country → state → city: three Selects where each child is disabled until its parent has a value (FormField helper says why), loading while its options fetch, and reset to \"\" when the parent changes. A composition, not a component — reach for Cascader only when it is ONE field.",
  "tags": [
    "form",
    "select",
    "dependent",
    "cascading",
    "address",
    "loading",
    "reset"
  ]
}
