Computed Properties and Watchers

Chapter 5
Computed Properties and Watchers
Deriving values that update themselves, and reacting to changes with side effects

Chapter 2 put a {{ price * quantity }} expression directly in the template. That works, but repeating it or running heavier logic inline gets messy fast. Computed properties are Vue's clean way to derive a reactive value from other reactive values; watchers are for running a side effect when something changes. Both will feel familiar from React's useMemo and useEffect — but with Vue's automatic dependency tracking.

computed — A Derived Reactive Value

<script setup> import { ref, computed } from 'vue'; const price = ref(100); const quantity = ref(2); const total = computed(() => price.value * quantity.value); </script> <template> <p>Total: {{ total }}</p> </template>

computed(() => ...) returns a ref-like value that automatically recalculates whenever any reactive value it reads changes — here, whenever price or quantity changes. Two important properties: it's cached (only recomputes when a dependency actually changes, not on every render), and its dependencies are auto-tracked — there's no dependency array to maintain, unlike React's useMemo. Vue knows total depends on price and quantity simply because the function read them. In the template it's used as {{ total }} (auto-unwrapped, no .value); in script you'd read total.value.

Identical in spirit to Angular's computed signal
If the Angular course's computed() signal felt clean, this is the same idea — a derived value that tracks its own dependencies and caches. Vue's computed predates Angular's by years; the convergence is no accident, as both frameworks moved toward fine-grained reactivity. Coming from React, think "useMemo with no dependency array, and you read it like a value."

Computed vs a Method

<!-- a method: re-runs on every single render --> <p>{{ calculateTotal() }}</p> <!-- a computed: cached, only re-runs when price/quantity change --> <p>{{ total }}</p>

You could call a regular method in the template instead — but a method re-runs every time the component re-renders for any reason, even if its inputs didn't change. A computed caches its result and only recomputes when a dependency genuinely changes. For anything beyond a trivial expression, prefer computed — the caching is free performance.

Filtering a List with computed

const todos = ref([/* ... */]); const showCompleted = ref(false); const visibleTodos = computed(() => showCompleted.value ? todos.value : todos.value.filter((t) => !t.done) );

This is the clean fix for Chapter 4's "don't combine v-if and v-for" rule: a computed property returns exactly the items that should be shown, and the template does v-for="todo in visibleTodos". The filter recomputes automatically when either todos or showCompleted changes — the same "filter before mapping" pattern React used in Fundamentals Chapter 6, here expressed reactively.

watch — Running a Side Effect on Change

import { ref, watch } from 'vue'; const searchTerm = ref(''); watch(searchTerm, (newValue, oldValue) => { console.log(`changed from "${oldValue}" to "${newValue}"`); // e.g. fire a search request here });

watch runs a callback whenever a specific reactive source changes, receiving both the new and old value. It's for genuine side effects in response to a change — fetching data, saving to localStorage, logging — the role React's useEffect with a dependency plays. The key distinction from computed: computed derives and returns a value; watch does something and returns nothing. If you find yourself assigning to another ref inside a watch, a computed is usually the better tool.

watchEffect — Auto-Tracked Watching

import { watchEffect } from 'vue'; watchEffect(() => { console.log(`total is now ${price.value * quantity.value}`); }); // runs immediately, then again whenever price or quantity changes

watchEffect is a variant that runs immediately and re-runs whenever any reactive value it reads changes — automatically tracked, no explicit source to name. It's closer in feel to React's useEffect with auto-detected dependencies. Use watch when you need the old value or want to watch one specific source; use watchEffect when you just want "re-run this whenever anything it uses changes."

Don't overuse watch — reach for computed first
A very common beginner pattern is watching one ref to manually update another — that's almost always a computed in disguise, and the computed version is simpler, cached, and less bug-prone. Reserve watch/watchEffect for true side effects (I/O, logging, imperative DOM work). The mental rule: "deriving a value → computed; doing something → watch."
VueReactAngular
computed(() => ...)useMemo (no dep array)computed() signal
watch(src, cb)useEffect with a depeffect() / RxJS watch
watchEffect(cb)useEffect (auto deps)effect() signal

Coding Challenges

Challenge 1

Build a component with firstName and lastName refs and a fullName computed property combining them, with inputs to edit each — confirming fullName updates automatically.

📄 View solution
Challenge 2

Build a todo list with a "show completed" checkbox, using a computed visibleTodos that filters based on the checkbox, then v-for over visibleTodos — the clean fix for not combining v-if and v-for.

📄 View solution
Challenge 3

Build a component with a count ref and a watch that logs the new and old value whenever count changes, plus a watchEffect that logs a message referencing count — observing how the two differ (watchEffect runs immediately, watch does not).

📄 View solution

Chapter 5 Quick Reference

  • computed(() => ...) — a cached, auto-tracked derived value (= useMemo with no dep array)
  • Prefer computed over a method for derived values — it caches; a method re-runs every render
  • A computed filter is the clean fix for "don't combine v-if and v-for" (Chapter 4)
  • watch(source, (new, old) => ...) — runs a side effect on change, with old/new values
  • watchEffect(() => ...) — runs immediately and re-runs on any read dependency's change
  • Rule of thumb: deriving a value → computed; doing something → watch
  • Next chapter: components and props