How do you make a Vue 3 computed() writable, and what happens when you assign to a getter-only one?
answer
- an object instead of a function
- the setter writes the sources
- read-back comes from the getter
- dev-only readonly warning
basics
~10 sPass computed() an object with get and set; assigning to .value calls set, which should update the source refs. Assigning to a getter-only computed changes nothing and, in development builds, logs a readonly warning.
solid answer
~40 s`computed()` is getter-only by default. To make it writable, pass `{ get, set }`: assigning `fullName.value = 'Ada Lovelace'` calls `set('Ada Lovelace')`, and the setter's job is to write the *source* state, here `firstName` and `lastName`. The computed never stores the assigned value; the next read runs `get` again, so what you read back is whatever the getter derives from the updated sources. Assigning to a getter-only computed does nothing, and a development build logs `Write operation failed: computed value is readonly`. Separately, the value a computed returns should be treated as a read-only snapshot: pushing into a computed array or editing a computed object is a bug, because the next recomputation discards it. Writable computeds are rare; a typical use is binding `v-model` to a derived value.
code
ts · 14 linesimport { ref, computed } from 'vue'
const celsius = ref(20)
const fahrenheit = computed({
get: () => celsius.value * 9 / 5 + 32,
set: (f: number) => {
celsius.value = (f - 32) * 5 / 9
}
})
fahrenheit.value = 212
console.log(celsius.value) // 100
console.log(fahrenheit.value) // 212, re-derived from celsiusgo deeper
Recall the { get, set } form and that assigning to a plain computed does nothing except a development warning.
Explain that the setter writes the sources and the read-back always comes from the getter, and show the lossy-setter trap with a name that has no space.
Judge when a writable computed is clearer than an explicit update function, and catch code that mutates a computed's returned array or object.
Treat writable computeds as a narrow tool for two-way views over several sources, and keep them to setters with an obvious inverse of the getter.
## Getter-only by default In Vue 3, `computed(() => ...)` creates a **readonly computed ref**. Its `.value` is whatever the getter last returned, and it is recomputed when a reactive dependency changes. There is no storage slot you can write into: the value is *derived*, not held. If code assigns to it anyway, nothing changes. In a development build Vue logs the warning `Write operation failed: computed value is readonly`; production builds strip the warning and the assignment is silently ignored. Either way, the computed keeps returning what its getter produces. ## Making it writable Pass an object with two functions instead of a single getter: ```vue <script setup> import { ref, computed } from 'vue' const firstName = ref('John') const lastName = ref('Doe') const fullName = computed({ get() { return firstName.value + ' ' + lastName.value }, set(newValue) { ;[firstName.value, lastName.value] = newValue.split(' ') } }) </script> <template> <input v-model="fullName" /> </template> ``` Now `fullName.value = 'Ada Lovelace'` calls `set('Ada Lovelace')`. The setter writes `firstName` and `lastName`; those are dependencies of the getter, so the next read of `fullName.value` recomputes to `'Ada Lovelace'`. ## The setter writes sources, the getter decides what you read The key mental model is that a writable computed is still **derived**: - `set` receives the assigned value and must translate it into changes to the source state. - The assigned value is never cached; reading back always goes through `get`. - If the translation loses information, the read-back differs from what was assigned. The `fullName` setter above shows the trap. Assign `'Ada'` (no space): `split(' ')` returns `['Ada']`, so `firstName` becomes `'Ada'` and `lastName` becomes `undefined`. The next read returns `'Ada undefined'`, not `'Ada'`. A robust setter validates or normalises its input (split on the first space, default the last name to an empty string) so that `get(set(x))` returns something sensible. | Operation | Getter-only computed | Writable computed | |---|---|---| | Read `.value` | runs or reuses `get` | runs or reuses `get` | | Assign `.value = x` | ignored, dev warning | calls `set(x)` | | What you read after assigning | unchanged value | `get` re-derived from updated sources | ## Do not mutate the returned value Writability is about **assigning** `.value`. It does not make the returned object or array a place to store edits. The Vue docs describe a computed's return value as a temporary snapshot: every time the sources change, a new snapshot is produced. So: 1. `sortedItems.value.push(newItem)` edits a snapshot the next recomputation throws away, and it may corrupt the source if the computed returned the source array itself. 2. The correct fix is to change the source (`items.value.push(newItem)`) and let the computed derive the new result. 3. If you need a user-editable copy of a derived value, that is local state initialised from the derivation, not a computed. ## When a writable computed is worth it Writable computeds are uncommon. They fit where a single value is a **view over several sources** and something wants to assign to that view: - a `fullName` field bound with `v-model` that splits into first and last name; - a checkbox bound to "all selected", whose setter selects or clears every row; - a unit conversion, such as a Fahrenheit input over a Celsius ref. In each case the setter has a clear inverse of the getter. When no clean inverse exists, an explicit function (`setFullName(value)`) is usually clearer than a setter with hidden rules. ## Mistakes interviewers listen for - Believing an assignment "overrides" a getter-only computed until a dependency changes. It does not; the assignment is dropped. - Writing a setter that assigns to the computed itself (`fullName.value = v` inside `set`). The setter must write the sources; assigning the computed from inside its own setter calls the setter again, recursing until the stack overflows. - Expecting the read-back to equal the assigned value when the setter is lossy. - Using a writable computed to hide a side effect, such as an API call in `set`. A setter can run on every keystroke of a `v-model` input; put network work in an explicit handler or a watcher. - Mutating the returned object or array and expecting the change to persist. ## Version note The `{ get, set }` form has existed since Vue 3.0. Since 3.4 the getter also receives the previous computed value as its argument (`get(previous)`), which is unrelated to writability but appears in the same signature.
- After fullName.value = 'Ada' with a setter that splits on a space, what does fullName.value return?`'Ada undefined'`. The setter destructures `['Ada']`, so `firstName` becomes `'Ada'` and `lastName` becomes `undefined`, and the getter re-derives from both. The computed never stores the assigned string. A setter should normalise its input so the read-back matches what the user typed.
- Is it acceptable to push into the array a Vue computed returns?No. The return value is a derived snapshot; the next recomputation replaces it, and if the getter returned the source array itself the push silently edits the source outside the intended path. Push into the source state and let the computed derive the new array.
saying these in an interview costs you the question
- Assigning to a getter-only computed overrides it until a dependency changes.
- A writable computed stores the assigned value and returns it on the next read.
- Assigning to a readonly computed throws and breaks rendering.
- Making a computed writable lets you push into the array it returns.
- The setter should set the computed's own value, not its sources.