Computed Properties and Watchers

By Syncfusion®, Inc. · Last updated

computed() and caching, computed vs functions, writable computed, watch() on refs, getters, reactive objects and many sources, deep, immediate and once, watchEffect() and flush timing, stopping a watcher, onWatcherCleanup() for stale requests, and when to use computed or a watcher.

Derived Values and Side Effects

  • Some values come from other values. A bill total comes from price and plates.
  • Derived value = a value worked out from state. Vue gives you computed() for it.
  • Sometimes you must do something when state changes: save, log or load data.
  • Side effect = an action outside the screen, such as saving or calling a server.
  • Vue gives you watch() and watchEffect() for side effects.

Simple lesson: computed makes values. Watchers do jobs.

A Real-World Example

  • Meera's bill shows the price, the plates, the GST and the total.
  • GST and total must always match the plates.
  • She does not want to update them by hand after every click.
  • computed() keeps them right by itself.
  • What happened: each click changed plates.
  • subtotal, gst and total worked themselves out again.

What You Will Learn

  1. How to make a value with computed().
  2. Why computed values are cached, and how that differs from a function.
  3. How to make a computed value you can also set.
  4. How to run code when state changes with watch().
  5. The deep, immediate and once options.
  6. How watchEffect() finds what to watch by itself.
  7. When a watcher runs: flush.
  8. How to stop a watcher and cancel old work.
  9. When to pick computed and when to pick a watcher.

computed()

  • computed(() => ...) takes a function that returns a value.
  • It gives back a read-only ref. Use .value in the script, the name in the template.
  • Vue remembers which state the function read. These are its dependencies.
  • When a dependency changes, the computed value is worked out again.

Animation: price and plates feed into the computed subtotal. When plates changes, subtotal is worked out again and the screen updates. A watcher on total runs a side effect, writing to the console

price plates

computed subtotal

screen watch: save

plates changes → subtotal is worked out again → screen updates → watcher saves

Computed values feed the screen. Watchers run jobs on the side.

Computed vs a Function

  • A function in the template runs again on every screen update.
  • A computed value is cached. It runs again only when a dependency changes.
  • Cached = the answer is saved and reused.
  • Click "Refresh time" below. The function runs again. The computed value does not.
  • What happened: "Refresh time" logged only "function ran".
  • "Add a dish" changed a dependency, so both ran.

A computed function must only work out a value. Do not change other state, call a server or change the page inside it. Put those jobs in a watcher.

Writable Computed

  • A computed value is read-only by default.
  • You can give it a get and a set to make it writable.
  • Setting it runs set, which changes the real state.

Simple lesson: computed() works a value out from state and saves the answer until the state changes.

watch()

  • watch(source, callback) runs the callback when the source changes.
  • The callback gets the new value and the old value.
  • The source can be:

    • a ref
    • a getter (a function that returns a value)
    • a reactive object
    • an array of sources
  • To watch one property of a reactive object, use a getter: () => order.plates.
  • What happened: each button changed one source.
  • Only the watchers for that source ran. Look at the Console below the preview.

watch(order.plates, ...) does not work. It passes the number 1, not something Vue can watch. Wrap it in a getter: watch(() => order.plates, ...).

deep, immediate and once

  • deep = also notice changes inside nested objects and arrays.
  • A watched reactive object is deep by default. A getter or ref to an object is not.
  • deep: 1 (a number, Vue 3.5) watches only one level down.
  • Careful: when you change inside an object, the new and old values are the same object. Compare a copied value instead, such as a getter that returns order.plates.
  • immediate = run the callback once right away, not only after a change.
  • once = run the callback one time only, then stop (Vue 3.4).
  • What happened: "immediate" logged before any click.
  • push changed the array inside the ref. deep: true noticed it.
  • "once" logged on the first table change only.

Simple lesson: watch() runs your job when a chosen source changes.

watchEffect()

  • watchEffect(fn) runs fn right away.
  • Vue tracks every reactive value the function reads.
  • When any of them changes, the function runs again.
  • You do not list the sources. Vue finds them.
  • Trap: in an async function, Vue only tracks what is read before the first await. Read the values you need at the top.

When a Watcher Runs: flush

  • By default a watcher runs before Vue updates the screen. This is flush: 'pre'.
  • flush: 'post' runs it after the screen update, so it can read the new page.
  • flush: 'sync' runs it at once, on every single change. Use it rarely.
  • watchPostEffect() and watchSyncEffect() are short names for these.

Simple lesson: watchEffect watches whatever it reads. flush: 'post' waits for the new screen.

Stopping and Cleaning Up

  • A watcher stops by itself when its component is removed.
  • But a watcher created later, inside a setTimeout or after an await, is not linked to the component. Stop it yourself.
  • watch() and watchEffect() return a stop function. Call it to stop early.
  • Stale request = an old request that finishes after a newer one and shows old data.
  • onWatcherCleanup(fn) (Vue 3.5) runs fn before the watcher runs again, or when it stops.
  • Use it to cancel the old request with AbortController.
  • What happened: click "Dish 2" then "Dish 3" quickly.
  • The request for dish 2 was cancelled. Only dish 3 was shown.
  • After "Stop watching", clicks change id but load nothing.

Real apps call a real server, which may take any amount of time. In this lab, the pretend API at api.meeraskitchen.dev answers after the delay you ask for, and logs each finished request in the Console.

Simple lesson: stop watchers you no longer need, and cancel old work in onWatcherCleanup.

Computed or Watcher?

QuestionUse
Do I need a value to show?computed
Do I need to save, log, call a server or touch the page?watch or watchEffect
Do I need the old value too?watch
Should it run at the start?watchEffect, or watch with immediate
Is it many sources I do not want to list?watchEffect

Simple lesson: values are computed. Jobs are watched.

TL;DR

  • computed(() => x) = a cached, read-only value worked out from state.
  • Dependencies = the state a computed or watcher reads. A change reruns it.
  • Writable computed = computed({ get, set }).
  • watch(source, cb) = runs cb(new, old) when the source changes.
  • Getter source = () => obj.prop, to watch one property.
  • Options = deep, immediate, once, flush.
  • watchEffect(fn) = runs now, then again when anything it read changes.
  • Stop = the function returned by watch() / watchEffect().
  • onWatcherCleanup(fn) = cancel old work before the next run (Vue 3.5).

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
Side effects in computedA computed can save or fetchUse a watcher for jobsStrange loops and repeated requests
watch(obj.count, ...)Any value can be watchedUse a getter: () => obj.countA warning, and the watcher never runs
Watcher for a simple totalWatch and set another refUse computedMore code, and values can drift apart
Ref to an array, no deeppush triggers watch(list, ...)Add { deep: true }The watcher misses the change
Expecting a run at the startwatch runs once at the startAdd { immediate: true }Nothing happens until a change
Reading the page in a pre watcherThe DOM is already newUse flush: 'post'You read the old text
No cleanup for requestsOld replies do not matterAbort in onWatcherCleanupOld data replaces new data
Reading state after await in watchEffectEverything it reads is trackedRead sources before the first awaitChanges are missed
Comparing new and old in a deep watchOld keeps the previous objectWatch a getter of the value you compareNew and old look the same

Check Yourself

Exercises

What to Remember

  • computed is for values. It is cached until a dependency changes.
  • Never change state or call servers inside a computed.
  • watch is for jobs. Use a getter to watch one property.
  • deep, immediate, once and flush change when and how a watcher runs.
  • Cancel old requests with onWatcherCleanup to avoid stale data.

Next: Dynamic Rendering