React to state changes with custom side effects.
1The Watch Function
While computed properties allow us to declaratively compute derived values, there are cases where we need to perform 'side effects' in reaction to state changes - for example, mutating the DOM, or fetching data from an async API based on the result of an operation.
2Watch Sources
watch's first argument can be different types of reactive 'sources': it can be a ref (including computed refs), a reactive object, a getter function, or an array of multiple sources. Remember to use a getter () => obj.prop for specific properties of reactive objects.
3Deep and Immediate Watchers
By default, watchers are lazy. Use { immediate: true } to force the callback to run immediately. Also, watching a ref containing an object does not trigger on deep mutations unless you use { deep: true }.
4Step-by-Step Breakdown
Computed properties are great for deriving state. But what if you need to perform a "side effect" when a variable changes, like calling an API or saving to localStorage?
For side effects, Vue provides the watch function. It allows you to "watch" a reactive variable and run a callback whenever it changes.
Which function is used to perform a side effect (like an API call) in response to a reactive variable changing?
- →computed
- →watch
- →observe
When watching a ref, you just pass the ref itself (without .value). The callback gives you the new value and the old value.
If you want to watch a property INSIDE a reactive object, you must pass a getter function, not the property directly.
How do you correctly watch a specific property (like settings.theme) inside a reactive object?
- →state.theme
- →() => state.theme
- →state.theme.value
By default, watch is lazy: it only runs when the watched variable actually changes. If you want it to run immediately upon creation as well, use { immediate: true }.
If you are watching a deeply nested object or array and want to detect mutations inside it, use { deep: true }.
Which option forces a watcher to trigger immediately when the component is created, before the data even changes?
- →deep
- →immediate
- →flush
Rule of thumb: If you are returning a new value based on other values, use computed. If you are performing an action (API, console.log, localStorage), use watch.
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Watchers Driving Live-Region Announcements
A watch(currentStep, (step) => { announcement.value = `Step ${step} of 5` }) is the standard way to trigger a screen-reader announcement as a side effect of state changing, since the resulting ref can be bound to an aria-live region — exactly the 'side effect' use case watch is designed for, as opposed to computed, which must stay pure.
<div aria-live="assertive">{{ announcement }}</div>SEO Implications
- 1
watch Callbacks Never Run During Server-Side Rendering
Like onMounted, a watch() callback only fires in response to a value changing after the component is alive in the browser — during SSR there is no 'change' event stream, so watchers never execute server-side and cannot be relied on to produce any content that needs to be present in the initial crawlable HTML.
Best Practices
Prefer watchEffect Only When the Dependencies Are Genuinely Dynamic
watchEffect automatically tracks whatever reactive values its callback reads, which is convenient but makes the actual dependency list implicit and harder to audit; when you already know exactly which source should trigger the effect, an explicit watch(source, callback) documents that intent directly in the code.
Frequent Bugs
Passing a property path directly instead of a getter: watch(state.age, ...).
state.age is evaluated once, immediately, to a plain value at the moment watch() is called — Vue then watches that static snapshot, which never changes again, instead of watching the property. Pass a getter function, watch(() => state.age, callback), so Vue can re-evaluate it on every reactivity trigger.
Real-World Examples
Debounced Auto-Save on Form Edits
An article editor watches its content ref and, inside the callback, clears and restarts a setTimeout, auto-saving to the backend 2 seconds after the user stops typing rather than on every keystroke.
let saveTimer;
watch(content, (val) => {
clearTimeout(saveTimer);
saveTimer = setTimeout(() => saveDraft(val), 2000);
});