Vue: destructured props lose reactivity
Fix Vue 3 reactivity loss when destructuring reactive objects or props, and choose between toRefs, toRef, and reactive-aware destructuring.
Symptoms
- A prop or reactive field updates in state but the template does not re-render
- Values captured from a destructured object stay at their initial value
- Computed properties never recompute after a change
Possible causes
- Destructuring a reactive() object copies primitives, losing reactivity
- Destructuring props in setup() takes plain values, not refs
- Passing a computed value into a plain variable before use
- Using a primitive from a ref without .value inside a function
Diagnosis steps
Check whether the value is a ref or a plain copy
console.log(isRef(value))Interpretation
A false result after destructuring confirms reactivity was lost at the destructure.
Convert with toRefs or toRef
Interpretation
toRefs(props) yields refs that stay connected; plain destructuring yields snapshots.
Trace where the template reads the value
Interpretation
Templates unwrap refs automatically, but only if they still read the ref, not a copied primitive.
Root cause
Vue tracks reactivity through object property access and ref identity. Destructuring a reactive object reads the current value once and copies it, breaking the dependency link, so later mutations update the source but not the copied variable. In the Composition API, props destructured in setup() behave the same way.
Fix
Use toRefs(props) or toRef(props, key) to obtain references that remain reactive, or read props directly (props.title) rather than destructuring. For reactive() objects, keep field access on the object or convert to refs with toRefs. In templates, functions should reference a ref via .value or the unwrapped reactive field.
Verify the fix
Update the parent prop and observe; the child template reflects the new value immediately, and isRef on the converted value returns true.
Symptoms
A child component receives updated props but its template keeps showing the old value. Computed properties that depend on the value never recompute.
Diagnosis
Check whether the value is still a ref after destructuring:
import { isRef } from 'vue';
const { title } = defineProps(['title']); // plain value, not a ref
console.log(isRef(title)); // false
Fix
Keep the reference with toRefs:
import { toRefs } from 'vue';
const props = defineProps(['title', 'count']);
const { title, count } = toRefs(props); // reactive refs
// template reads stay reactive; in script use title.value
For reactive objects, access fields on the object or wrap with toRefs.
Verification
Change the parent value and confirm the child re-renders. isRef(title) now returns true, proving the reactivity link survived.
Additional Notes
Reactivity in Vue is tied to property access on a reactive object or to a ref identity, so copying a value out with destructuring reads it once and breaks the link. Converting with the reference helpers preserves that connection and keeps templates updating, while reading through the original object works just as well. The mistake is easy to miss because the initial value displays correctly and only later updates fail to appear, so a quick check of whether the extracted value is still a reference is the fastest way to confirm the diagnosis.