The Problem
Vue 3 provides two ways to create reactive state: ref() for primitives and reactive() for objects. Using them incorrectly leads to lost reactivity, confusing bugs, and code that is hard to maintain.
Symptoms
- Reactive object replacement loses reactivity
- Destructuring reactive objects breaks reactivity
- Template shows "[object Object]" instead of values
- State updates do not trigger re-renders
Common Mistakes
Mistake 1: Replacing Reactive Object
```javascript // WRONG: Replacing loses reactivity const user = reactive({ name: 'John', age: 30 });
function reload() { const fresh = { name: 'Jane', age: 25 }; Object.assign(user, fresh); // This works, but: // user = fresh; // WRONG: Cannot reassign reactive! } ```
Mistake 2: Destructuring Loses Reactivity
```javascript const user = reactive({ name: 'John', age: 30 });
// WRONG: name and age are plain values, not reactive const { name, age } = user;
// CORRECT: Use toRefs const { name, age } = toRefs(user); ```
Mistake 3: Nested Reactivity with ref
// This actually works - ref auto-unwraps nested reactive objects
const state = ref({ user: reactive({ name: 'John' }) });
// But it's confusing. Pick one approach.How to Fix It
Guidelines: When to Use What
```javascript // Use ref for: primitives, single values, replaceable objects const count = ref(0); const name = ref('John'); const user = ref({ name: 'John', age: 30 }); // Can be replaced
// Use reactive for: fixed-shape objects that are never replaced const form = reactive({ name: '', email: '', password: '' }); ```
Fix: Safe Destructuring of Reactive Objects
```javascript const user = reactive({ name: 'John', age: 30 });
// Option 1: toRefs const { name, age } = toRefs(user); console.log(name.value); // Still reactive
// Option 2: toRef for a single property const nameRef = toRef(user, 'name');
// Option 3: Use ref instead of reactive const userRef = ref({ name: 'John', age: 30 }); const { name, age } = userRef.value; // Destructure the unwrapped value ```
Fix: Replaceable Object State
```javascript // Use ref when you need to replace the entire object const user = ref(null);
async function loadUser(id) { user.value = await fetchUser(id); // Safe replacement } ```
Fix: Form State with reactive
```javascript const form = reactive({ name: '', email: '', errors: {} });
function resetForm() { // Cannot do: form = { name: '', email: '', errors: {} } // Instead: form.name = ''; form.email = ''; form.errors = {}; // OR: Object.assign(form, { name: '', email: '', errors: {} }); } ```