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}`
})
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}`)
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)