JAVASCRIPT
Reactive Side Effects with watchEffect
Utilize Vue 3's `watchEffect` to automatically track reactive dependencies and execute side effects, simplifying reactive logic and ensuring automatic re-execution when dependencies change.
<template>
<div class="watch-effect-example">
<h1>Watch Effect Example</h1>
<p>Current Product ID: {{ productId }}</p>
<button @click="changeProduct">Next Product</button>
<p>Search Query: <input v-model="searchQuery" placeholder="Type to search..." /></p>
<p>Is Loading: {{ isLoading }}</p>
<p>Product Details: {{ productDetails }}</p>
<p>Search Results: {{ searchResults }}</p>
</div>
</template>
<script setup>
import { ref, watchEffect } from 'vue';
const productId = ref(1);
const searchQuery = ref('');
const isLoading = ref(false);
const productDetails = ref(null);
const searchResults = ref([]);
const fetchProductDetails = async (id) => {
isLoading.value = true;
productDetails.value = null;
console.log(`Fetching details for product ${id}...`);
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 500 + Math.random() * 500));
productDetails.value = { id, name: `Product ${id} Name`, price: (id * 10).toFixed(2) };
isLoading.value = false;
};
const performSearch = async (query) => {
if (!query) {
searchResults.value = [];
return;
}
isLoading.value = true;
searchResults.value = [];
console.log(`Searching for: ${query}...`);
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 300 + Math.random() * 300));
searchResults.value = [`Result for "${query}" 1`, `Result for "${query}" 2`];
isLoading.value = false;
};
// watchEffect will automatically track productId and call fetchProductDetails
watchEffect(async (onCleanup) => {
// Simulate a "cancellation" for previous fetches if dependencies change quickly
let cancelled = false;
onCleanup(() => {
cancelled = true;
console.log('Product fetch cancelled.');
});
if (productId.value) {
await fetchProductDetails(productId.value);
if (!cancelled) {
console.log('Product details updated.');
}
}
});
// watchEffect will automatically track searchQuery and call performSearch
// A debounce could be added here for real-world scenarios
watchEffect(async (onCleanup) => {
let cancelled = false;
onCleanup(() => {
cancelled = true;
console.log('Search cancelled.');
});
// Small debounce for search input
const timeout = setTimeout(async () => {
if (searchQuery.value) {
await performSearch(searchQuery.value);
if (!cancelled) {
console.log('Search results updated.');
}
} else {
searchResults.value = []; // Clear results if query is empty
}
}, 300);
onCleanup(() => clearTimeout(timeout));
});
const changeProduct = () => {
productId.value++;
};
</script>
<style scoped>
.watch-effect-example {
font-family: Arial, sans-serif;
padding: 20px;
border: 1px solid #eee;
border-radius: 8px;
max-width: 600px;
margin: 20px auto;
background-color: #f9f9f9;
}
h1 {
color: #333;
}
p {
margin-bottom: 10px;
}
button {
padding: 8px 15px;
margin-right: 10px;
background-color: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #368a68;
}
input {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
width: 200px;
}
</style>
How it works: `watchEffect` in Vue 3 provides a simple way to run a function immediately and automatically re-run it whenever its reactive dependencies change. Unlike `watch`, you don't explicitly list the dependencies; `watchEffect` automatically tracks them during its first execution. This snippet demonstrates two `watchEffect` instances: one fetching product details when `productId` changes and another performing a search when `searchQuery` updates. The `onCleanup` function is particularly useful for managing asynchronous operations, allowing you to cancel pending requests if the watched dependencies change before the previous operation completes, preventing race conditions.