
The surprising bug
You have a reactive() object and want to “replace” it with a fresh one — reset a form, load new data, swap a user. So you reassign the variable:
import { reactive } from 'vue'
let user = reactive({ name: 'Ada', age: 36 })
function loadNewUser() {
// 💥 reassigning the variable
user = reactive({ name: 'Grace', age: 41 })
}
Call loadNewUser() and your template still shows Ada, 36. Nothing updates. Why?
Why it doesn’t work
reactive() returns a proxy bound to the original object. When you write user = reactive({...}), user now points at a different proxy — a brand-new reactive wrapper. But every template, computed, and watcher captured a reference to the original proxy through the component’s setup scope. They never see the new one, so no dependency is ever notified.
In short:
reactive()reactivity is per-object, not per-variable.- Reassigning the variable swaps the object, but not the bindings already pointing at the old one.
Fix 1 — use ref() for objects (recommended)
ref() wraps the value in a .value getter/setter, so reassigning .value is tracked:
import { ref } from 'vue'
const user = ref({ name: 'Ada', age: 36 })
function loadNewUser() {
user.value = { name: 'Grace', age: 41 } // ✅ reactive
}
Fix 2 — mutate in place
If you prefer reactive(), keep the same object and overwrite its properties:
import { reactive } from 'vue'
const user = reactive({ name: 'Ada', age: 36 })
function loadNewUser() {
// ✅ mutate instead of reassign
Object.assign(user, { name: 'Grace', age: 41 })
}
Rule of thumb
- Need to replace the whole object/array? Use
ref(). - Need nested reactivity and only ever mutate fields?
reactive()is fine. - Never reassign a
reactive()variable and expect the UI to follow.
A 30-second gotcha that saves an afternoon of “why isn’t my view updating” debugging.