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()andwatchEffect()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,gstandtotalworked themselves out again.
What You Will Learn
- How to make a value with
computed(). - Why computed values are cached, and how that differs from a function.
- How to make a computed value you can also set.
- How to run code when state changes with
watch(). - The
deep,immediateandonceoptions. - How
watchEffect()finds what to watch by itself. - When a watcher runs:
flush. - How to stop a watcher and cancel old work.
- 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
.valuein 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
getand asetto 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.
pushchanged the array inside the ref.deep: truenoticed it.- "once" logged on the first table change only.
Simple lesson: watch() runs your job when a chosen source changes.
watchEffect()
watchEffect(fn)runsfnright 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
asyncfunction, Vue only tracks what is read before the firstawait. 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()andwatchSyncEffect()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
setTimeoutor after anawait, is not linked to the component. Stop it yourself. watch()andwatchEffect()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) runsfnbefore 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
idbut 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?
| Question | Use |
|---|---|
| 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)= runscb(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
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Side effects in computed | A computed can save or fetch | Use a watcher for jobs | Strange loops and repeated requests |
watch(obj.count, ...) | Any value can be watched | Use a getter: () => obj.count | A warning, and the watcher never runs |
| Watcher for a simple total | Watch and set another ref | Use computed | More code, and values can drift apart |
Ref to an array, no deep | push triggers watch(list, ...) | Add { deep: true } | The watcher misses the change |
| Expecting a run at the start | watch runs once at the start | Add { immediate: true } | Nothing happens until a change |
| Reading the page in a pre watcher | The DOM is already new | Use flush: 'post' | You read the old text |
| No cleanup for requests | Old replies do not matter | Abort in onWatcherCleanup | Old data replaces new data |
Reading state after await in watchEffect | Everything it reads is tracked | Read sources before the first await | Changes are missed |
| Comparing new and old in a deep watch | Old keeps the previous object | Watch a getter of the value you compare | New and old look the same |
Check Yourself
Exercises
What to Remember
computedis for values. It is cached until a dependency changes.- Never change state or call servers inside a computed.
watchis for jobs. Use a getter to watch one property.deep,immediate,onceandflushchange when and how a watcher runs.- Cancel old requests with
onWatcherCleanupto avoid stale data.
Next: Dynamic Rendering