Skip to main content
Vue.js for Beginners to Advanced
CHAPTER 12 Beginner

Computed Properties and Watchers

Updated: May 18, 2026
5 min read

# CHAPTER 12

Computed Properties and Watchers

1. Chapter Introduction

computed and watch are Vue's two tools for reactive programming beyond templates. Computed properties are for derived state (values calculated from other reactive data). Watchers are for side effects (running code when data changes). Knowing when to use each is a mark of Vue expertise.

2. Learning Objectives

  • Create computed properties for derived state.
  • Understand computed caching.
  • Use watch for side effects.
  • Use watchEffect for automatic dependency tracking.
  • Know when to use computed vs watch.

3. Computed Properties

vue
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
<script setup>
import { ref, computed } from &#039;vue'

const price = ref(100)
const quantity = ref(3)
const taxRate = ref(0.18)
const cart = ref([
  { id: 1, name: &#039;MacBook', price: 1999, qty: 1 },
  { id: 2, name: &#039;iPhone', price: 999, qty: 2 },
  { id: 3, name: &#039;AirPods', price: 249, qty: 1 },
])
const searchQuery = ref(&#039;')
const users = ref([
  { name: &#039;Alice', active: true },
  { name: &#039;Bob', active: false },
  { name: &#039;Carol', active: true },
])

// Computed: auto-updates when price, quantity, or taxRate changes
const subtotal = computed(() => price.value * quantity.value)
const tax = computed(() => subtotal.value * taxRate.value)
const total = computed(() => subtotal.value + tax.value)
const totalFormatted = computed(() => `$${total.value.toFixed(2)}`)

// Computed from array — recalculates only when cart changes
const cartTotal = computed(() =>
  cart.value.reduce((sum, item) => sum + item.price * item.qty, 0)
)
const cartItemCount = computed(() =>
  cart.value.reduce((sum, item) => sum + item.qty, 0)
)

// Computed filter
const activeUsers = computed(() => users.value.filter(u => u.active))
const filteredCart = computed(() =>
  cart.value.filter(item =>
    item.name.toLowerCase().includes(searchQuery.value.toLowerCase())
  )
)
</script>

<template>
  <p>Subtotal: {{ subtotal }}</p>
  <p>Tax (18%): {{ tax.toFixed(2) }}</p>
  <p>Total: {{ totalFormatted }}</p>

  <p>{{ cartItemCount }} items in cart — {{ cartTotal }}</p>
  <p>Active users: {{ activeUsers.length }}</p>
</template>

4. Writable Computed

vue
1234567891011121314151617181920212223
<script setup>
import { ref, computed } from &#039;vue'

const firstName = ref(&#039;Alice')
const lastName = ref(&#039;Johnson')

// Writable computed — has both get and set
const fullName = computed({
  get() {
    return `${firstName.value} ${lastName.value}`
  },
  set(newValue) {
    const parts = newValue.split(&#039; ')
    firstName.value = parts[0] ?? &#039;'
    lastName.value = parts.slice(1).join(&#039; ') ?? ''
  }
})
</script>

<template>
  <input v-model="fullName" />  <!-- Editing fullName updates first/last name -->
  <p>First: {{ firstName }}, Last: {{ lastName }}</p>
</template>

5. watch — Watching for Changes

vue
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
<script setup>
import { ref, watch, watchEffect } from &#039;vue'

const userId = ref(1)
const user = ref(null)
const searchQuery = ref(&#039;')
const count = ref(0)

// Basic watch — runs when userId changes
watch(userId, async (newId, oldId) => {
  console.log(`ID changed from ${oldId} to ${newId}`)
  // Fetch new user data
  user.value = await fetchUser(newId)
})

// Watch with options
watch(userId, (newVal) => {
  console.log(&#039;User ID:', newVal)
}, {
  immediate: true,  // Run immediately on mount
  once: true        // Run only once
})

// Watch multiple sources
watch([userId, searchQuery], ([newId, newQuery], [oldId, oldQuery]) => {
  console.log(&#039;Either userId or searchQuery changed')
})

// Deep watch — detects nested property changes
const userObj = ref({ name: &#039;Alice', address: { city: 'NYC' } })
watch(userObj, (newVal) => {
  console.log(&#039;User object changed:', newVal)
}, { deep: true })

// Specific nested property
watch(() => userObj.value.address.city, (newCity) => {
  console.log(&#039;City changed to:', newCity)
})

// watchEffect — auto-tracks dependencies (no source needed)
watchEffect(() => {
  // Runs immediately, then re-runs when any reactive deps inside change
  console.log(&#039;Count or query changed:', count.value, searchQuery.value)
  // If you reference count.value and searchQuery.value, both are tracked
})

// Stop a watcher
const stop = watchEffect(() => { /* ... */ })
stop()  // Stop watching
</script>

6. computed vs watch vs watchEffect

text
1234567891011121314151617
┌────────────────┬──────────────────────────────────────────────┐
│ Feature        │ Use Case                                     │
├────────────────┼──────────────────────────────────────────────┤
│ computed       │ Derive a value from reactive data            │
│                │ (filter, sort, format, calculate)            │
├────────────────┼──────────────────────────────────────────────┤
│ watch          │ Side effects when specific data changes      │
│                │ (fetch data, save to localStorage, log)      │
├────────────────┼──────────────────────────────────────────────┤
│ watchEffect    │ Side effects with auto-dependency tracking   │
│                │ (good for non-specific side effects)         │
└────────────────┴──────────────────────────────────────────────┘

RULE:
- Need a VALUE from reactive data? → computed
- Need to DO SOMETHING when data changes? → watch/watchEffect
- NEVER put side effects (fetch, localStorage) in computed!

7. Common Mistakes

  • Putting async code in computed: computed(async () => ...) does NOT work. Use watch for async operations.
  • Mutating other reactive state in computed: Computed should be pure — no side effects, no mutation.

8. MCQs

Question 1

Computed properties are cached based on?

Question 2

When do you use watch over computed?

Question 3

What does { immediate: true } in watch do?

Question 4

{ deep: true } in watch is for?

Question 5

watchEffect vs watch?

Question 6

Writable computed requires?

Question 7

Can computed be async?

Question 8

Watch multiple sources?

Question 9

Stop a watchEffect by?

Question 10

Computed accessing items.value.length recalculates when?

9. Interview Questions

  • Q: What is the difference between a computed property and a method in Vue?
  • Q: When would you use watchEffect vs watch?

10. Summary

Computed properties are Vue's most powerful feature for deriving state — they cache intelligently and update automatically. Watchers are the tool for async operations, API calls, and persistence side effects. The rule is simple: need a value → computed, need a side effect → watch.

11. Next Chapter Recommendation

In Chapter 13: Vue Lifecycle Hooks, we understand the component lifecycle from creation to unmounting and learn how to hook into each phase with onMounted, onUpdated, and onUnmounted.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·