Watchers: watch and watchEffect
A watcher is a function that Vue calls when certain data changes. computed returns a new value, while a watcher performs an action: saves data, sends a request, writes to the console. Such actions are called side effects.
The watch function
The first argument is what to track, the second is what to do on a change:
<!-- src/App.vue -->
<script setup>
import { ref, watch } from 'vue'
const query = ref('')
watch(query, (newValue, oldValue) => {
console.log(`Search: was "${oldValue}", now "${newValue}"`)
})
</script>
<template>
<input v-model="query" placeholder="Find a task">
</template>Type "br" into the field — the browser console prints two lines: first was "", now "b", then was "b", now "br". The v-model directive links the field to the variable; it is covered in detail in the forms lesson.
Saving tasks
A typical job for watch is saving the list to localStorage after every change:
<script setup>
import { ref, watch } from 'vue'
const saved = localStorage.getItem('todos')
const todos = ref(saved ? JSON.parse(saved) : [])
watch(todos, (value) => {
localStorage.setItem('todos', JSON.stringify(value))
}, { deep: true })
</script>After a page reload the tasks are still there. The deep: true option makes the watcher fire when fields inside tasks change, such as done, not only when the whole array is replaced.
Options
| Option | What it does |
|---|---|
deep: true | Tracks changes of nested fields |
immediate: true | Calls the function right away on creation, not only on change |
once: true | Fires only once |
Tracking part of an object
To watch a single field, pass a getter function that returns it:
<script setup>
import { ref, watch } from 'vue'
const filter = ref({ query: '', onlyActive: false })
watch(() => filter.value.onlyActive, (value) => {
console.log('Only active:', value)
})
</script>The watcher fires when onlyActive is toggled and ignores changes of query.
The watchEffect function
watchEffect finds dependencies on its own: it runs the function right away and remembers which reactive data it read.
<script setup>
import { ref, watchEffect } from 'vue'
const todos = ref([])
watchEffect(() => {
document.title = `Tasks: ${todos.value.length}`
})
</script>The browser tab title becomes "Tasks: 0" at once and changes every time a task is added or removed.
watch | watchEffect | |
|---|---|---|
| Source | Given explicitly | Found automatically |
| First run | On change | Immediately |
| Old value | Available | Not available |
Common mistake
Passing a value to watch instead of a source:
<script setup>
watch(filter.value.query, (value) => { /* ... */ })
</script>filter.value.query is just a string at the moment of the call; Vue prints a warning and does not track it. Pass the ref itself or a function: watch(() => filter.value.query, ...).