Skip to content

Instantly share code, notes, and snippets.

@unlocomqx
Created October 11, 2025 06:55
Show Gist options
  • Save unlocomqx/cd6e4f958097031874eae443e35f507a to your computer and use it in GitHub Desktop.
Save unlocomqx/cd6e4f958097031874eae443e35f507a to your computer and use it in GitHub Desktop.
<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