Skip to content

Instantly share code, notes, and snippets.

@unlocomqx
Created October 11, 2025 06:53
Show Gist options
  • Save unlocomqx/e88a60f57ee033f737c91a7c37b15091 to your computer and use it in GitHub Desktop.
Save unlocomqx/e88a60f57ee033f737c91a7c37b15091 to your computer and use it in GitHub Desktop.
import React, { useState, useEffect, useCallback } from 'react';
function StateCitySelector() {
// State initialization
const [states, setStates] = useState([]);
const [selectedState, setSelectedState] = useState('');
const [cities, setCities] = useState([]);
const [selectedCity, setSelectedCity] = useState('');
// Loading state management (mimics Svelte's implicit $effect.pending())
const [isLoadingStates, setIsLoadingStates] = useState(true);
const [isLoadingCities, setIsLoadingCities] = useState(false);
// 1. EFFECT: Fetch initial states on mount (Svelte's top-level await)
useEffect(() => {
const fetchInitialStates = async () => {
try {
const initialStates = await getStates();
setStates(initialStates);
// Auto-select the first state
if (initialStates.length > 0) {
setSelectedState(initialStates[0]);
}
} catch (error) {
console.error('Error fetching states:', error);
} finally {
setIsLoadingStates(false);
}
};
fetchInitialStates();
}, []); // Run only once
// 2. EFFECT: Fetch cities whenever selectedState changes (Svelte's $derived/await)
useEffect(() => {
if (!selectedState) {
setCities([]);
setSelectedCity('');
return;
}
const fetchCities = async (state) => {
let isCurrent = true; // Cleanup flag
setIsLoadingCities(true);
setCities([]);
try {
const newCities = await getCities(state);
if (isCurrent) {
setCities(newCities);
// Auto-select the first city
setSelectedCity(newCities.length > 0 ? newCities[0] : '');
}
} catch (error) {
console.error(`Error fetching cities for ${state}:`, error);
} finally {
if (isCurrent) {
setIsLoadingCities(false);
}
}
};
fetchCities(selectedState);
// Cleanup function
return () => {
isCurrent = false;
};
}, [selectedState]); // Reruns when selectedState changes
// Handlers
const handleStateChange = useCallback((event) => {
setSelectedState(event.target.value);
}, []);
const handleCityChange = useCallback((event) => {
setSelectedCity(event.target.value);
}, []);
// Render logic
if (isLoadingStates) {
return <p>Loading initial data...</p>;
}
return (
<div>
{/* State Select */}
<select value={selectedState} onChange={handleStateChange}>
{states.map((state) => (
<option key={state} value={state}>
{state}
</option>
))}
</select>
{/* City Select */}
<select
value={selectedCity}
onChange={handleCityChange}
// Disabled when cities are loading or no state is selected
disabled={isLoadingCities || !selectedState}
>
{/* Conditional content for loading */}
{isLoadingCities && <option value="" disabled>Loading cities...</option>}
{!isLoadingCities && cities.map((city) => (
<option key={city} value={city}>
{city}
</option>
))}
</select>
<p>Selection: {selectedCity || 'N/A'}, {selectedState || 'N/A'}</p>
</div>
);
}
export default StateCitySelector;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment