Country, state and city dropdown in React.
Load countries first, states after a country is selected, and cities after a state is selected. Clear dependent values when a parent changes, and ignore late responses. This guide includes the component running in the demo.
Tested with @countrystatecity/[email protected], using dataset v5.2.0-export.1. Uses a CDN snapshot; no API key required.
Try the dependent dropdown.
Try India → Maharashtra → Mumbai, or choose a different country or territory. Starting the demo loads public package data from jsDelivr.
The demo uses the published package. Returned data depends on the package snapshot. A place can be absent, renamed or classified differently from the needs of your application.
1. Install the browser package.
npm install @countrystatecity/[email protected]Use React 18 or newer. This guide pins the tested package version. The browser package loads JSON from jsDelivr on demand and caches responses in memory. It does not bundle the entire city dataset into your initial page, and it needs network access when data is first loaded. Review package release notes before upgrading.
2. Add the React component.
The file below is the exact component used above. Native labels, selects and buttons work without a UI library. Apply your own CSS to match your application.
View the complete React component
"use client";
import { useEffect, useId, useState } from "react";
import { clearCache, getCountries, getStatesOfCountry, getCitiesOfState } from "@countrystatecity/countries-browser";
const UNASSIGNED = "__unassigned__";
const LABELS = { country: "Country or territory", state: "State or region", city: "City or place" };
/** Load one dependent list and ignore responses after its parent selection changes. */
function LocationSelect({ kind, country = "", state = "", value, onChange }) {
const id = useId();
const [result, setResult] = useState({ items: [], loading: true, error: "" });
const [attempt, setAttempt] = useState(0);
const enabled = kind === "country" || (kind === "state" ? Boolean(country) : Boolean(country && state));
useEffect(() => {
let active = true;
if (!enabled) return;
const request = kind === "country" ? getCountries()
: kind === "state" ? getStatesOfCountry(country)
: getCitiesOfState(country, state === UNASSIGNED ? null : state);
request.then((items) => {
if (!Array.isArray(items) || !items.every((item) => item && typeof item.name === "string" && item.name.trim() &&
(kind === "country" || item.country_code === country) &&
(kind === "city" ? Number.isSafeInteger(item.id) && item.id > 0 && item.state_code === (state === UNASSIGNED ? null : state)
: typeof item.iso2 === "string" && (kind === "country" ? /^[A-Z]{2}$/ : /^[A-Z0-9-]{1,16}$/).test(item.iso2))) ||
new Set(items.map((item) => kind === "city" ? item.id : item.iso2)).size !== items.length) {
throw new TypeError("Invalid location list");
}
if (active) setResult({ items, loading: false, error: "" });
}).catch(() => {
if (active) setResult({ items: [], loading: false, error: "Could not load a valid location list. Retry the request." });
});
return () => { active = false; };
}, [kind, country, state, attempt, enabled]);
const { items, loading, error } = result;
const empty = enabled && !loading && !error && items.length === 0;
const placeholder = !enabled ? (kind === "state" ? "Choose a country first" : "Choose a state or the unassigned option first")
: loading ? "Loading…" : `Choose ${kind}`;
return (
<div>
<label htmlFor={id}>{LABELS[kind]}</label>
<select id={id} name={kind} value={value} onChange={(event) => onChange(event.target.value)}
disabled={!enabled || loading || Boolean(error) || (empty && kind !== "state")} aria-describedby={`${id}-status`}>
<option value="">{placeholder}</option>
{items.map((item) => <option key={kind === "city" ? item.id : item.iso2}
value={kind === "city" ? String(item.id) : item.iso2}>{item.name}</option>)}
{kind === "state" && enabled && !loading && !error && <option value={UNASSIGNED}>No listed state (unassigned places)</option>}
</select>
<p id={`${id}-status`} role="status">
{enabled && loading ? "Loading locations…" : error || (empty ? "No entries returned. This can also mean the CDN is unavailable." : "")}
</p>
{enabled && !loading && (error || empty) && <button type="button" onClick={() => {
clearCache();
setResult({ items: [], loading: true, error: "" });
setAttempt((count) => count + 1);
}}>Retry {LABELS[kind].toLowerCase()}</button>}
</div>
);
}
/** A native React dropdown: changing a parent clears and remounts its dependent lists. */
export default function LocationDropdown() {
const [country, setCountry] = useState("");
const [state, setState] = useState("");
const [city, setCity] = useState("");
return (
<fieldset>
<legend>Choose a location</legend>
<LocationSelect kind="country" value={country} onChange={(code) => {
setCountry(code); setState(""); setCity("");
}} />
<LocationSelect key={`states-${country}`} kind="state" country={country} value={state} onChange={(code) => {
setState(code); setCity("");
}} />
<LocationSelect key={`cities-${country}-${state}`} kind="city" country={country} state={state} value={city} onChange={setCity} />
<output aria-label="Selected location" aria-live="polite">
{city ? JSON.stringify({ country_code: country, state_code: state === UNASSIGNED ? null : state, city_id: Number(city) })
: "Choose a location to see its codes and city ID."}
</output>
</fieldset>
);
}
For a React application, import and render LocationDropdown inside your page or form. Once a city is selected, the example displays its numeric ID with the country and state codes. Validate submitted values and their parent relationships on your server before storing them.
3. Use it in Next.js App Router.
Save the downloaded component as app/components/LocationDropdown.jsx. Keep its "use client" directive. Your page can remain a Server Component:
// app/page.jsx
import LocationDropdown from "./components/LocationDropdown";
export default function Page() {
return <LocationDropdown />;
}The country list loads in an Effect after the component mounts. In React development Strict Mode, an Effect may run an extra setup/cleanup cycle. Cleanup ignores obsolete results; it does not cancel network requests already started by the package.
Handle the cases a basic example misses.
- Changing parents: country changes clear state and city; state changes clear city. Keyed child components start with empty options so cities from the previous country cannot remain selectable.
- Slow responses: Effect cleanup ignores old results after a list unmounts. This matters when someone changes countries before the first request finishes.
- Empty lists and CDN errors: in version 3.0.1, the state and city helpers can return an empty array after a fetch failure. An empty list is not proof that the location has no states or cities. Retry clears the package cache before requesting data again. Offer a manual-entry path in your application.
- Cities with no state: the unassigned option calls
getCitiesOfState(countryCode, null). It checks the country-level city file in the package. Some countries have no such file or no returned records; the result can be empty. Map the unassigned UI marker tonullbefore saving, as the output shows. - Names and IDs: use country codes, country-scoped state codes and city IDs rather than city names as identifiers. Duplicate city names exist. Check migration notes before updating stored IDs to a new data release.
- Address coverage: these lists include settlements and other place types, and administrative levels vary. A dropdown cannot replace postal-address validation or a free-text fallback.
Choose a package or the hosted API.
Use the browser package for a versioned snapshot loaded from a CDN without an API key. For a fully local dataset, review the bundled npm data packages and the build setup of your application.
Use the hosted API when you want the dataset currently served there, server-side requests, search or autocomplete on eligible plans. Compare field access, endpoints and quotas. Keep private API credentials on your server and apply permitted-use restrictions to browser integrations.
Questions about the React dropdown.
Does this React dropdown need an API key?
No. This example uses @countrystatecity/countries-browser, which fetches versioned JSON from jsDelivr. It requires a network connection to load data. It does not call the authenticated CSC API or consume an API request quota.
Can I use this in Next.js?
Yes. Keep the use client directive in LocationDropdown.jsx, install the package, and render the component from an App Router page. Lists load in the browser after the component mounts; this is not server-side location search.
Why do state and city selections reset?
A city belongs to a particular country and, when present, a state. Changing the country clears both dependent selections; changing the state clears the city. Keyed list components and Effect cleanup prevent late responses from restoring an old selection's options.
Is the dropdown an address validator?
No. Selecting a listed place does not verify a street address, postcode or delivery destination. Coverage and administrative levels vary; provide a manual-entry path when the right place is missing.