A computed property is a value derived from other reactive values. You describe how to work it out; Vue works out when to re-run it.
const isAuthenticated = computed(() => Boolean(token.value));
const roles = computed(() => user.value?.roles ?? []);
const isAdmin = computed(() => roles.value.includes("ADMIN"));
const isCreator = computed(() => roles.value.includes("CREATOR"));Each reads other reactive state and returns a value. Nothing calls them, nothing refreshes them,
and nothing can forget to. isAdmin is correct the instant user changes,
because roles depends on user and isAdmin depends on
roles.
A computed is a ref, so it is .value in script and bare in a template:
if (auth.isAdmin.value) { /* ... */ }<button v-if="auth.isAdmin">Manage creators</button>Why not just a method?
You could write a function and call it from the template. It would produce the right answer. The difference is caching.
const meta = computed(() => STATUS_META[props.status] ?? fallback); // cached
function getMeta() { return STATUS_META[props.status] ?? fallback; } // not cachedA computed caches its result and only recalculates when one of the reactive values it read changes. A method runs every single time it appears in a render.
For a template that reads the value three times, that is three calls versus one. For a computed over a list — filtering a hundred reels — it is the difference between doing that work once and doing it on every unrelated re-render.
The rule: if a template reads it, make it a computed. Use a method for something the template calls with arguments, like a formatter.
Caching is by dependency, not by time
A computed with no reactive dependencies never updates:
// Computed once, at first read, and then never again -- Date.now() is not
// reactive, so nothing will ever invalidate the cache.
const now = computed(() => Date.now());The cache is invalidated by a dependency changing. No dependencies means nothing to invalidate it.
A computed must be pure
Reactive values in, a value out. No fetching, no mutating other state, no touching the DOM.
// WRONG -- a computed is not the place for this.
const results = computed(async () => {
return await api.search({ q: term.value });
});That does not work and cannot be made to. A computed is evaluated lazily, on read, possibly
several times and possibly never, so a side effect inside one fires at unpredictable moments. An
async computed returns a Promise rather than a value, and the template renders
[object Promise].
Side effects belong in a watcher. That is the next lesson, and this is the distinction it turns on.
Optional chaining is your friend
Notice user.value?.roles ?? [] in the auth store. A computed frequently runs before
the data it depends on has arrived — user is null until the session is
read. Returning an empty array instead of throwing means the template renders an empty state rather
than the component erroring.
const hasVideo = computed(() => Boolean(props.reel.video?.url));Same idea: reel.video is absent on a poster-only reel, and ?. makes
that a false rather than a crash.
Writable computeds
A computed is read-only by default; assigning to one warns. When you genuinely need two-way derived state, give it a getter and a setter:
const fullName = computed({
get() {
return `${first.value} ${last.value}`.trim();
},
set(value) {
const [f, ...rest] = value.split(" ");
first.value = f ?? "";
last.value = rest.join(" ");
},
});
fullName.value = "Ada Lovelace"; // writes through to first and lastThis is rarer than it looks and worth being suspicious of. The common case it gets used for —
v-model on a prop — has a better answer in lesson 12.
Chaining is fine
A computed can depend on another, as isAdmin depends on roles. Vue
resolves the graph and each node recalculates at most once per change. Build small derived values and
compose them rather than one large computed that does everything — the small ones are easier to name,
easier to test, and cache more precisely.
Next: Watchers — for everything a computed must not do.