100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
JavaScript

reactive() and ref()

A deep dive into Vue 3's two core reactivity primitives — ref() for any value type and reactive() for objects — and when to use each.

Reactivity FundamentalsBeginner10 min readJul 9, 2026
Analogies

reactive() and ref()

Vue 3's Composition API exposes two primary functions for creating reactive state: ref() and reactive(). Both are built on the same underlying Proxy-based reactivity system, but they differ in what kind of values they wrap and how you interact with them in JavaScript code. Understanding the distinction — and the tradeoffs of each — is essential to writing correct, idiomatic Composition API code.

🏏

Cricket analogy: Vue's ref() and reactive() are like two ways a scorer can track the game — a single running total (ref, wraps any value with .value) versus a full scoreboard object (reactive, wraps objects only) — both powered by the same tracking system but suited to different situations.

ref(): Reactivity for Any Value

ref() wraps a value of any type — primitive (string, number, boolean) or object — in a special reactive object with a single .value property. Reading or writing .value is what triggers dependency tracking and reactive updates. Vue's compiler and template system automatically "unwrap" refs, so inside a <template> you write {{ count }} rather than {{ count.value }}. Because ref() works uniformly for primitives and objects, and because reassigning .value to an entirely new object still works reactively, ref() is the recommended default for most standalone reactive state in the Composition API.

🏏

Cricket analogy: A single "current score" ref works whether it's holding a plain number or an entire innings-summary object — you read and write it via .value in code, but the scoreboard display auto-unwraps it so fans just see the number, and swapping in a whole new innings object still updates the display live.

reactive(): Deep Reactivity for Objects

reactive() takes an object (or array, Map, Set) and returns a Proxy of that object where property access and mutation are automatically tracked, without needing a .value wrapper — you just read and write properties directly, e.g. state.count++. However, reactive() has two notable constraints: it only works with object types (calling reactive() on a primitive like a number does nothing useful), and the reactivity is tied to the Proxy identity, meaning if you destructure properties out of a reactive object into standalone variables, or reassign the entire object, you lose the reactive connection. This is why Vue provides toRefs() to safely destructure a reactive object's properties into individual refs that retain reactivity.

🏏

Cricket analogy: A reactive() scoreboard object tracks runs and wickets directly (scoreboard.runs++) without a .value wrapper, but it only works because it's an object — and if a commentator pulls "runs" out into its own variable, that copy stops updating live; toRefs() is how you safely hand out individual live-updating stats to each commentator.

vue
<script setup>
import { ref, reactive, toRefs } from 'vue'

// ref: works for primitives and objects alike
const count = ref(0)

// reactive: for a cohesive object of related state
const form = reactive({
  username: '',
  email: '',
  agreedToTerms: false,
})

// Destructuring reactive() directly would break reactivity;
// toRefs() preserves it by returning individual refs
const { username, email } = toRefs(form)

function incrementCount() {
  count.value++
}

function submitForm() {
  form.agreedToTerms = true
}
</script>

ref() and reactive() are interoperable: assigning an object to a ref (ref({ ... })) internally calls reactive() on that object if it's a plain object, and reactive() objects can contain nested refs, which are automatically unwrapped when accessed as properties (but not when accessed as array elements). This nested unwrapping behavior is a frequent source of subtle bugs, so it's worth testing carefully.

Directly destructuring a reactive() object — const { count } = reactive({ count: 0 }) — breaks reactivity because count becomes a plain, disconnected number. Always use toRefs() (or toRef() for a single property) when you need to pull properties out of a reactive object while preserving their reactive connection.

  • ref() wraps any value type and requires .value access in script code (auto-unwrapped in templates).
  • reactive() wraps objects/arrays/Maps/Sets in a Proxy with direct property access, no .value needed.
  • reactive() only works on object types; calling it on a primitive has no reactive effect.
  • Destructuring a reactive() object loses reactivity; use toRefs() to preserve it.
  • ref() is generally the safer, more flexible default for standalone reactive state.
  • Nested refs inside a reactive() object are auto-unwrapped when accessed as object properties.

Practice what you learned

Was this page helpful?

Topics covered

#JavaScript#VueJsStudyNotes#WebDevelopment#ReactiveAndRef#Reactive#Ref#Reactivity#Any#StudyNotes#SkillVeris