Watchers | Vue.js

Watchers in Vue are state change observers. Whenever there is a state change, the watcher triggers a callback. In cases where there is a need to perform any side effects for a state change, watchers are used.

watch


watch's first argument is the state we want to observe for change. State sources can be of type ref, reactive object, getter function, or array of sources.


Below is an example of Composition API. Here, the state kgs is of type ref that is being tracked.

<script setup>
import { ref, watch } from 'vue'

const kgs = ref(1)
const ounces = ref(35.274)
const pounds = ref(2.20462)

watch(kgs, (newKgs) => {
  ounces.value = 35.274*Number(newKgs)
  pounds.value = 2.20462*Number(newKgs)
})
</script>

<template>
  <p>
    <p>Weight Converter</p>
    <div>
      <label>kgs: </label>
      <input v-model="kgs" />
    </div>
    <div>
      <div>ounces: {{ounces}}</div>
    </div>
    <div>
      <div>pounds: {{pounds}}</div>
    </div>
  </p>
</template>


To run the above example, paste the code in Vue SFC Playground


Note: For reactive objects, we have to use getter functions to watch the property of a reactive object.


The following code will not work.

<script setup>
import { ref, watch, reactive } from 'vue'
  
const weights = reactive({ kgs: 1 })
const ounces = ref(35.274)
const pounds = ref(2.20462)

// passing it like this will provide the watcher with a number
watch(weights.kgs, (newKgs) => {
  ounces.value = 35.274*Number(newKgs)
  pounds.value = 2.20462*Number(newKgs)
})
</script>

<template>
  <p>
    <p>Weight Converter</p>
    <div>
      <label>kgs: </label>
      <input v-model="weights.kgs" />
    </div>
    <div>
      <div>ounces: {{ounces}}</div>
    </div>
    <div>
      <div>pounds: {{pounds}}</div>
    </div>
  </p>
</template>


A getter function should be used.

<script setup>
import { ref, watch, reactive } from 'vue'
  
const weights = reactive({ kgs: 1 })
const ounces = ref(35.274)
const pounds = ref(2.20462)

watch(
  () => weights.kgs, // getter
  (newKgs) => {
    ounces.value = 35.274*Number(newKgs)
    pounds.value = 2.20462*Number(newKgs)
  }
)
</script>

<template>
  <p>
    <p>Weight Converter</p>
    <div>
      <label>kgs: </label>
      <input v-model="weights.kgs" />
    </div>
    <div>
      <div>ounces: {{ounces}}</div>
    </div>
    <div>
      <div>pounds: {{pounds}}</div>
    </div>
  </p>
</template>


Here, if we pass the entire weights object instead of passing the getter for kgs, the watcher will watch for changes in the whole object tree, even the nested properties. It is a costly operation and may have performance implications.


watchEffect


watchEffect() closely resembles to useEffect() in React. It is used to perform some action (a side effect) when the component is mounted and then perform the same action after relevant state changes. watchEffect does so by tracking its reactive dependencies.
The above example can be re-written using watchEffect()

<script setup>
import { ref, watchEffect } from 'vue'

const kgs = ref(1)
const ounces = ref(35.274)
const pounds = ref(2.20462)

watchEffect(()=>{
  ounces.value = kgs.value*35.274;
  pounds.value = kgs.value*2.20462;
})
</script>

<template>
  <p>
    <p>Weight Converter</p>
    <div>
      <label>kgs: </label>
      <input v-model="kgs" />
    </div>
    <div>
      <div>ounces: {{ounces}}</div>
    </div>
    <div>
      <div>pounds: {{pounds}}</div>
    </div>
  </p>
</template>


Tracking changes stop when the component in which the watcher is created unmounts. However, one should stop the watcher manually; if created asynchronously to avoid memory leaks.

// a watcher in a setTimeout is created asynchronously
setTimeout(() => {
  watchEffect(() => {})
}, 100)


Note: Watchers should be created synchronously wherever possible.


Stay tuned for more concepts in Vue.js!

Divyajyoti Ukirde

Divyajyoti Ukirde

Senior Software Engineer | Investing in knowledge | with Passion for building stuff