Skip to content

ref and reactive

Vue gives you two APIs for creating reactive state: ref and reactive. Understanding the difference between them — and when to use each — is the foundation of working with Vue’s reactivity system.

ref wraps any value — primitive or object — in a reactive container:

import { ref } from 'vue'
const count = ref(0) // number
const name = ref('Alice') // string
const isActive = ref(true) // boolean
const person = ref({ name: 'Alice', age: 30 }) // object

To read or write the value in <script setup>, access .value:

console.log(count.value) // 0
count.value = 5 // triggers re-render
count.value++ // also triggers re-render
console.log(person.value.name) // 'Alice'
person.value.name = 'Bob' // triggers re-render

In templates, Vue auto-unwraps refs — you don’t write .value:

<template>
<p>{{ count }}</p> <!-- not count.value -->
<p>{{ person.name }}</p> <!-- not person.value.name -->
</template>

reactive makes a plain object deeply reactive — all nested properties are reactive automatically:

import { reactive } from 'vue'
const person = reactive({
name: 'Alice',
age: 30,
address: {
city: 'Portland'
}
})
person.name = 'Bob' // triggers re-render
person.address.city = 'Seattle' // also triggers re-render

No .value — you access properties directly, both in script and template.

<template>
<p>{{ person.name }}</p>
<p>{{ person.address.city }}</p>
</template>

The Vue team’s recommendation for most cases: prefer ref. Here’s why:

ref works for everything — primitives and objects. You always access it the same way (.value in script). reactive only works for objects and has a key limitation: you can’t destructure it without losing reactivity:

const state = reactive({ count: 0, name: 'Alice' })
// ❌ This breaks reactivity — count is now a plain number
const { count } = state
// ✅ This is fine — you keep the reactive reference
state.count++

ref doesn’t have this problem because the reference itself is reactive. In this course, FamilyTree’s Pinia store uses ref for all state.

In FamilyTree’s familyStore.ts, all state is declared with ref:

const people = ref<Person[]>([])
const focusPersonId = ref<string | null>(null)

This works with storeToRefs (Module 06) to safely destructure store state while keeping reactivity.

  1. Create a PersonForm.vue with a reactive form object: { name: '', birthYear: '' }.
  2. Bind each field to an input with v-model (you’ll learn the full details in Module 04).
  3. Display the current values using {{ form.name }} and {{ form.birthYear }}.
  4. Refactor to use ref for each field instead. Compare the code — notice the .value access pattern.
  • ref(value) wraps any value reactively; access with .value in script, auto-unwrapped in templates.
  • reactive(object) makes an object and all its properties deeply reactive; no .value needed.
  • Prefer ref — it works for all types and avoids destructuring pitfalls.
  • Destructuring a reactive object loses reactivity; use toRefs or keep property access on the object.