Watchers

  • reactively watch for changes in data and perform custom logic when those changes occur.

Basic Implementation

 <p>
    Ask a yes/no question:
    <input v-model="question" :disabled="loading" />
  </p>
  <p>{{ answer }}</p>
import { ref, watch } from 'vue'

const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')
const loading = ref(false)

// watch works directly on a ref
watch(question, async (newQuestion, oldQuestion) => {
  if (newQuestion.includes('?')) {
    loading.value = true
    answer.value = 'Thinking...'
    try {
      const res = await fetch('https://yesno.wtf/api')
      answer.value = (await res.json()).answer
    } catch (error) {
      answer.value = 'Error! Could not reach the API. ' + error
    } finally {
      loading.value = false
    }
  }
})

Eager watchers

  • By default, Vue.js watchers are lazy, meaning their callback functions are only executed when the watched data changes. However, there are situations where you might want the callback to run immediately and then again whenever the watched data changes.

  • To force a watcher's callback to execute immediately, you can include the immediate: true option when defining the watcher. This is useful, for example, when you need to fetch initial data and then re-fetch whenever relevant state changes.

watch(
  source,
  (newValue, oldValue) => {
    // executed immediately, then again when `source` changes
  },
  { immediate: true }
)

watchEffect()

  • Automatically tracks the dependencies of the callback.

  • In the example, the callback runs immediately, and during execution, it tracks todoId.value as a dependency. Whenever todoId.value changes, the callback is re-executed.

  • Simplifies and streamlines the process of watching reactive state, automatically handling dependency tracking without the need for explicit source values.

watchEffect(async () => {
  const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${todoId.value}`)
  data.value = await response.json()
})

Callback Flush Timing

  • When modify reactive state in Vue.js, it can trigger both Vue component updates and user-created watcher callbacks.

  • By default, watcher callbacks are called before Vue component updates.

  • This means that if attempt to access the DOM inside a watcher callback, the DOM will be in the state before Vue has applied any updates.

  • To access the DOM in a watcher callback after Vue has updated it, you can specify the flush: 'post' option when defining the watcher.

    • specify "flush:'post'"

watch(source, callback, {
  flush: 'post'
});
watchEffect(callback, {
  flush: 'post'
});
  • Convenience Alias: watchPostEffect():

import { watchPostEffect } from 'vue';

watchPostEffect(() => {
  /* executed after Vue updates */
});
  • By default, user-created watcher callbacks are executed before Vue component updates, but we can use the flush: 'post' option to ensure they run after updates, allowing to access the updated DOM state.

  • The watchPostEffect() alias is provided for clarity when using post-flush watchEffect().

Stopping a watcher

  • Watchers declared synchronously inside setup() or "script setup" are automatically bound to the owner component instance and will be stopped automatically when the component is unmounted.

  • If a watcher is created in an asynchronous callback, it won't be bound to the owner component and must be manually stopped to prevent memory leaks.

  • The key is to create watchers synchronously.

  • Example:

import { watchEffect } from 'vue'

// this one will be automatically stopped
watchEffect(() => {})

// ...this one will not!
setTimeout(() => {
  watchEffect(() => {})
}, 100)
  • To manually stop a watcher, use the handle function returned when the watcher is created. This applies to both watch and watchEffect.

const unwatch = watchEffect(() => {})

// ...later, when no longer needed
unwatch()

Last updated