DEV Community

Cover image for Watchers Are Usually the Wrong Answer
Yasin Demir
Yasin Demir

Posted on • Originally published at ysndmr.com

Watchers Are Usually the Wrong Answer

When I started with Vue's Composition API I used watch() constantly. State changes, something needs to happen — that's what watchers are for. Right?

Most of the time, no.

Watchers are for side effects: persisting to localStorage, calling an API when something changes, syncing with a library that Vue doesn't control. They're not for computing derived values, and they're not for keeping reactive state in sync with other reactive state.

The most common misuse:

const firstName = ref('')
const lastName = ref('')
const fullName = ref('')

watch([firstName, lastName], ([first, last]) => {
  fullName.value = `${first} ${last}`
})
Enter fullscreen mode Exit fullscreen mode

This is a computed property written the hard way. It runs asynchronously by default, it's harder to read, and it can produce frames where fullName is stale before the watcher fires.

const firstName = ref('')
const lastName = ref('')
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
Enter fullscreen mode Exit fullscreen mode

Always in sync. No timing edge cases. One line.

The other pattern that causes problems: chaining watchers. Watch A to update B, watch B to update C. I've done this. Every time I've eventually produced a cycle or a timing issue that was annoying to debug. If values derive from each other, computed handles it.

Where watch() actually belongs: real side effects. userId changes and you need to fetch new data, that's a watcher. The route changes and you need to update a third-party map instance, watcher. You need to debounce an API call on search input, watcher with a cleanup function.

The question I ask before adding a watch: is this a side effect, or is this a value that depends on other values? If it's the second thing, it's a computed. I've removed a lot of watchers by asking that.

Top comments (0)