Created
October 11, 2025 06:55
-
-
Save unlocomqx/cd6e4f958097031874eae443e35f507a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script setup> | |
import { ref, watch, onMounted, computed } from 'vue'; | |
// --- STATE (Equivalent to Svelte's $state / React's useState) --- | |
const states = ref([]); | |
const selectedState = ref(''); | |
const cities = ref([]); | |
const selectedCity = ref(''); | |
// --- LOADING STATE (Mimics Svelte's implicit $effect.pending()) --- | |
const isLoadingStates = ref(true); | |
const isLoadingCities = ref(false); | |
// --- ASYNC DATA FETCHING --- | |
// 1. Initial State Fetch (Equivalent to Svelte's top-level await / React's useEffect on mount) | |
onMounted(async () => { | |
try { | |
const initialStates = await getStates(); | |
states.value = initialStates; | |
// Auto-select the first state | |
if (initialStates.length > 0) { | |
selectedState.value = initialStates[0]; | |
} | |
} catch (error) { | |
console.error('Error fetching states:', error); | |
} finally { | |
isLoadingStates.value = false; | |
} | |
}); | |
// 2. City Fetching (Equivalent to Svelte's $derived/await / React's useEffect on dependency change) | |
// We use a watch function to react to changes in selectedState. | |
watch(selectedState, async (newState, oldState) => { | |
// Prevent fetching if the value is initial and empty | |
if (!newState) { | |
cities.value = []; | |
selectedCity.value = ''; | |
return; | |
} | |
// Vue's watch automatically handles debouncing and cancellation if a dependency changes again | |
// while the async operation is running, but we'll add manual handling for clarity. | |
// Manual cleanup variable for stricter control (similar to React's isCurrent flag) | |
let isCurrentFetch = true; | |
// Set loading state and clear old data | |
isLoadingCities.value = true; | |
cities.value = []; | |
try { | |
const newCities = await getCities(newState); | |
// Only update state if this is the most recent call | |
if (isCurrentFetch) { | |
cities.value = newCities; | |
// Auto-select the first city | |
selectedCity.value = newCities.length > 0 ? newCities[0] : ''; | |
} | |
} catch (error) { | |
console.error(`Error fetching cities for ${newState}:`, error); | |
if (isCurrentFetch) { | |
cities.value = []; | |
selectedCity.value = ''; | |
} | |
} finally { | |
if (isCurrentFetch) { | |
isLoadingCities.value = false; | |
} | |
} | |
// Note: If you return a function from the watch callback, it serves as a cleanup function. | |
// For this async pattern, a flag like 'isCurrentFetch' is often simpler. | |
}, { immediate: false }); // immediate: false prevents it from running on initial setup | |
// --- DERIVED/COMPUTED (The selection text is derived) --- | |
const selectionText = computed(() => | |
`Selection: ${selectedCity.value || 'N/A'}, ${selectedState.value || 'N/A'}` | |
); | |
</script> | |
<template> | |
<div v-if="isLoadingStates"> | |
<p>Loading initial data...</p> | |
</div> | |
<div v-else> | |
<select v-model="selectedState"> | |
<option v-for="state in states" :key="state" :value="state"> | |
{{ state }} | |
</option> | |
</select> | |
<select v-model="selectedCity" :disabled="isLoadingCities || !selectedState"> | |
<option v-if="isLoadingCities" value="" disabled>Loading cities...</option> | |
<option v-for="city in cities" :key="city" :value="city"> | |
{{ city }} | |
</option> | |
</select> | |
<p>{{ selectionText }}</p> | |
</div> | |
</template> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment