When Change Should Trigger Action: Understanding Watchers in Vue
Watchers in Vue observe reactive data and trigger side effects when changes occur, making them ideal for handling asynchronous actions, external interactions, and behavior tied to state changes.
At first, reactivity in Vue feels almost magical.
You change a value.
The UI updates automatically.
No extra effort.
No manual syncing.
But then you encounter a different kind of need.
Not just updating the UIâŚ
but doing something because a value changed.
When Updating Isnât Enough
Imagine a user selects a different item.
You donât just want to display it.
You want to fetch new data.
const userId = ref(1)
const user = ref(null)
watch(userId, async (newId) => {
const res = await fetch(`/api/users/${newId}`)
user.value = await res.json()
})Now the UI doesnât just reflect state.
It reacts to it.
A Different Kind of Reactivity
Computed properties derive values.
Watchers perform actions.
const firstName = ref('Rishi')
const lastName = ref('Indana')
const fullName = computed(() => firstName.value + ' ' + lastName.value)
watch(fullName, (newVal) => {
console.log('Full name changed to:', newVal)
})One creates a value.
The other reacts to it.
From Values to Effects
A watcher defines:
âWhen this changes⌠run this code.â
const search = ref('')
watch(search, (query) => {
console.log('Searching for:', query)
})This could easily become:
- an API call
- analytics tracking
- logging behavior
Why Computed Isnât Enough
Computed must stay pure.
Watchers handle side effects.
// â not appropriate for computed
watchEffect(async () => {
const res = await fetch('/api/data')
console.log(await res.json())
})Better:
watch(search, async (query) => {
const res = await fetch(`/api?q=${query}`)
})Now the effect is tied to a clear dependency.
Being Explicit About What You Observe
Watchers require you to define what to track.
watch(userId, (newVal) => {
console.log('User ID changed:', newVal)
})You can also watch multiple values:
watch([firstName, lastName], ([newFirst, newLast]) => {
console.log(newFirst, newLast)
})When Watching Becomes Subtle
Watching objects can be tricky.
const user = reactive({ name: 'Rishi' })
watch(user, () => {
console.log('User changed')
})This wonât trigger on user.name change.
To fix:
watch(user, () => {
console.log('User deeply changed')
}, { deep: true })Now nested changes are tracked.
Timing Matters Too
By default, watchers run only after changes.
watch(userId, (id) => {
fetchUser(id)
})To run immediately:
watch(userId, (id) => {
fetchUser(id)
}, { immediate: true })This removes the need for manual initial calls.
Cleaning Up After Yourself
Watchers can clean up side effects.
watch(search, (query, _, onCleanup) => {
const controller = new AbortController()
fetch(`/api?q=${query}`, { signal: controller.signal })
onCleanup(() => {
controller.abort()
})
})Now outdated requests are canceled.
When Things Start to Get Messy
Overusing watchers can create scattered logic.
watch(a, () => doSomething())
watch(b, () => doSomethingElse())
watch(c, () => doAnotherThing())Now behavior is spread across many watchers.
Harder to trace.
A Better Way to See It
Watchers connect your app to external effects.
watch(isLoggedIn, (val) => {
if (val) {
localStorage.setItem('auth', 'true')
}
})They bridge internal state and outside systems.
Knowing When to Use Them
Use watchers when you need side effects:
watch(theme, (val) => {
document.body.className = val
})Avoid them for simple transformations:
// better as computed
const fullName = computed(() => first + last)The Bigger Insight
Your app has two layers:
- data relationships
- behavioral reactions
const total = computed(() => price.value * qty.value)
watch(total, (val) => {
console.log('Total updated:', val)
})Computed defines the relationship.
Watcher reacts to it.
A Final Thought
When everything reacts to everything elseâŚ
it becomes harder to see why something happened.
Watchers give you power.
But they also require discipline.
Because in the end, the goal is not just to react to changeâŚ
but to do it in a way that remains understandable.
And sometimes, the best reactionâŚ
is no reaction at all.