Vue 3’s watch API lets you run arbitrary code whenever a reactive value changes. Its the reactive system’s hook for side-effects—loggging, validation, network requests, or synchronizing related state.
Listening to a single ref
<template>
<p>Counter: {{ count }}</p>
<button @click="count++">Increment</button>
</template>
<script setup>
import { ref, watch } from 'vue'
const count = ref(1)
watch(count, (newVal, oldVal) => {
alert(`Changed from ${oldVal} to ${newVal}`)
})
</script>
Two-way unit conversion
Below, two inputs stay synchronized by watching whichever field the user is editing.
<template>
<label>
Kilometers:
<input v-model="km" @focus="active = 'km'" />
</label>
<br />
<label>
Meters:
<input v-model="m" @focus="active = 'm'" />
</label>
<p id="log"></p>
</template>
<script setup>
import { ref, watch } from 'vue'
const km = ref(0)
const m = ref(0)
const active = ref('km')
watch(km, val => {
if (active.value === 'km') m.value = val * 1000
})
watch(m, val => {
if (active.value === 'm') km.value = val / 1000
})
watch(km, (newVal, oldVal) => {
document.getElementById('log').textContent =
`km changed from ${oldVal} to ${newVal}`
})
</script>
Triggering async work
The watcher below fires a GET request only when the question ends with a question mark.
<template>
<p>
<input v-model="query" placeholder="Ask something …" />
</p>
<p>{{ reply }}</p>
</template>
<script setup>
import { ref, watch } from 'vue'
import axios from 'axios'
const query = ref('')
const reply = ref('Questions must end with ?')
watch(query, q => {
if (!/[??]$/.test(q)) return
reply.value = 'Thinking …'
axios
.get('/api/answer', { params: { q } })
.then(res => (reply.value = res.data.answer))
.catch(err => (reply.value = 'Error: ' + err.message))
})
</script>