Watchers
Run side effects in response to reactive state changes using watch() and watchEffect().
Why Watchers Exist
Computed properties are for deriving a value. Watchers are for running a side effect — an API call, a console log, updating localStorage — when a reactive value changes. If you find yourself wanting a computed property that also "does something," you actually want a watcher.
watch()
watch() takes a reactive source and a callback, which runs whenever that source changes, receiving both the new and old value.
<script setup>import { ref, watch } from 'vue';
const searchTerm = ref('');
watch(searchTerm, (newValue, oldValue) => { console.log(`Search changed from "${oldValue}" to "${newValue}"`); fetchResults(newValue);});</script>Watching Multiple Sources
Pass an array of sources to react to any of several values changing.
watch([firstName, lastName], ([newFirst, newLast]) => { console.log('Name changed to', newFirst, newLast);});Deep Watching
By default, watching a reactive object only triggers on reassignment of the whole object, not changes to its nested properties. Pass { deep: true } to also react to nested mutations.
const user = reactive({ profile: { name: 'Ada' } });
watch(user, () => { console.log('Something inside user changed');}, { deep: true });deep: true recursively tracks every nested property, which can be expensive for large objects — watch a more specific, smaller piece of state when you can, instead of deep-watching a huge object.
watchEffect()
watchEffect() runs immediately, and automatically tracks whatever reactive values it reads inside — you don't need to explicitly list the source(s) up front.
import { ref, watchEffect } from 'vue';
const id = ref(1);
watchEffect(() => { console.log('Fetching data for user', id.value); // runs immediately, then again whenever id changes});watch vs watchEffect vs computed
| computed | watch | watchEffect | |
|---|---|---|---|
| Purpose | Derive a value | React to specific changes with a side effect | React to any tracked change with a side effect |
| Returns a value? | Yes | No | No |
| Runs immediately? | Lazily, on first access | No, only after a change (by default) | Yes, immediately |
| Dependencies | Automatically tracked | Explicitly listed | Automatically tracked |
FAQs
Yes — pass { immediate: true } as a third argument to watch() to run the callback once right away, in addition to on every change.
Both watch() and watchEffect() return a stop function you can call — but in a component's setup, they're automatically stopped when the component unmounts, so this is rarely needed manually.
Summary
Reach for computed() to derive a value, and watch()/watchEffect() to run a side effect in response to change. Next, you'll learn how to bind classes and inline styles dynamically.