Skip to content

Instantly share code, notes, and snippets.

@RoyalIcing
Last active October 8, 2025 04:50
Show Gist options
  • Select an option

  • Save RoyalIcing/706f8b1d7ef13f4eb40a90f9dd8a3ac2 to your computer and use it in GitHub Desktop.

Select an option

Save RoyalIcing/706f8b1d7ef13f4eb40a90f9dd8a3ac2 to your computer and use it in GitHub Desktop.
Simple interaction: one select changes the options of a second with a remote data source https://x.com/ryanflorence/status/1975402393534943529 https://stackblitz.com/edit/vitejs-vite-7qlpha4g?file=src%2FStateAndCityFieldset.tsx
import { setState } from "react";
class SharedState<State> extends EventTarget {
#state: State;
constructor(state: State) {
super();
this.#state = state;
}
get current() {
return this.#state;
}
modify(change: (state: State) => void) {
change(this.#state);
this.dispatchEvent(new CustomEvent("statechange"));
}
}
function useSharedState<State>(sharedState: SharedState<State>) {
return useSyncExternalStore(
(subscribe) => {
sharedState.addEventListener("statechange", subscribe);
return () => sharedState.removeEventListener("statechange", subscribe);
},
() => sharedState.current,
() => sharedState.current
);
}
const states = new Map<string, string>([
["AL", "Alabama"],
["AK", "Alaska"],
["AZ", "Arizona"],
["AR", "Arkansas"],
["CA", "California"],
["CO", "Colorado"],
["CT", "Connecticut"],
["DE", "Delaware"],
["FL", "Florida"],
["GA", "Georgia"],
["HI", "Hawaii"],
["ID", "Idaho"],
["IL", "Illinois"],
["IN", "Indiana"],
["IA", "Iowa"],
["KS", "Kansas"],
["KY", "Kentucky"],
["LA", "Louisiana"],
["ME", "Maine"],
["MD", "Maryland"],
["MA", "Massachusetts"],
["MI", "Michigan"],
["MN", "Minnesota"],
["MS", "Mississippi"],
["MO", "Missouri"],
["MT", "Montana"],
["NE", "Nebraska"],
["NV", "Nevada"],
["NH", "New Hampshire"],
["NJ", "New Jersey"],
["NM", "New Mexico"],
["NY", "New York"],
["NC", "North Carolina"],
["ND", "North Dakota"],
["OH", "Ohio"],
["OK", "Oklahoma"],
["OR", "Oregon"],
["PA", "Pennsylvania"],
["RI", "Rhode Island"],
["SC", "South Carolina"],
["SD", "South Dakota"],
["TN", "Tennessee"],
["TX", "Texas"],
["UT", "Utah"],
["VT", "Vermont"],
["VA", "Virginia"],
["WA", "Washington"],
["WV", "West Virginia"],
["WI", "Wisconsin"],
["WY", "Wyoming"],
["DC", "District of Columbia"],
["AS", "American Samoa"],
["GU", "Guam"],
["MP", "Northern Mariana Islands"],
["PR", "Puerto Rico"],
["UM", "United States Minor Outlying Islands"],
["VI", "Virgin Islands, U.S."],
]);
const statesToCities = new SharedState(new Map<string, readonly string[]>());
function loadCitiesForState(state: string) {
if (statesToCities.current.has(state)) {
return;
}
const url = new URL("/api/cities");
url.searchParams.set("state", state);
let loadCount = 0;
async function load() {
if (loadCount >= 3) {
return;
}
loadCount++;
await fetch(url)
.then((res) => res.json())
.then((cities) => {
statesToCities.modify(map => map.set(state, cities));
})
.catch((error) => {
window.reportError(error);
return load();
});
}
load();
}
function StateAndCityFieldset(): JSX.Element {
const [selectedState, setSelectedState] = useState<string>();
const [selectedCity, setSelectedCity] = useState<string>();
const statesToCitiesMap = useSharedState(statesToCities);
const cities = statesToCitiesMap.get(selectedState);
return (
<fieldset>
<label for="state">
City
<select
id="state"
value={selectedState}
onChange={(event) => {
const state = event.target.value;
setSelectedState(state);
setSelectedCity(undefined);
loadCitiesForState(state);
}}
>
{Array.from(cities, ([code, name]) => (
<option key={code} value={code}>
{name}
</option>
))}
</select>
</label>
<label for="city">
City
<select
id="city"
value={selectedCity}
disabled={cities === undefined}
onChange={(event) => {
const city = event.target.value;
setSelectedCity(city);
}}
>
{cities === undefined && <option disabled>Loading…</option>}
{Array.from(cities, city => (
<option key={city} value={city}>
{city}
</option>
))}
</select>
</label>
</fieldset>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment