For Vue 3 Composition API state, use ref() by default: it works with primitives and objects, and Vue recommends it as the primary API for declaring reactive state. Choose reactive() when a stable, object-shaped value is easier to work with through direct property access. The key tradeoff is .value syntax versus keeping and using the same reactive proxy.
Quick comparison: ref() and reactive()
| Question | ref() |
reactive() |
|---|---|---|
| What can it hold? | Primitives such as numbers, strings and booleans, as well as objects. | Objects, including arrays and collection types such as Map and Set; not primitives. |
| How do you access it in JavaScript? | Through .value, such as count.value++. |
Through properties on the returned proxy, such as state.count++. |
| What becomes reactive? | The ref and its value; object values are deeply converted by default. | The returned proxy and its properties; nested objects are converted deeply as accessed. |
| Can you replace the state container? | Yes. Assign a new value to .value. |
Not while preserving the original proxy connection. Keep using the same proxy. |
| What is the main gotcha? | Remembering .value in JavaScript and that automatic unwrapping depends on context. |
Replacing the proxy or destructuring a primitive property can disconnect access from reactivity. |
Vue’s Reactivity Fundamentals guide says: “Due to these limitations, we recommend using ref() as the primary API for declaring reactive state.”
As an Amazon Associate I earn from qualifying purchases.
Use ref() for most state
A ref wraps a value in a ref object. In JavaScript, read or update the wrapped value with .value. That makes it suitable for primitive state and for values that may be replaced altogether.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →import { ref } from 'vue'
const count = ref(0)
count.value++
const selectedItem = ref({ name: 'Notebook' })
selectedItem.value = { name: 'Pen' }
When a ref is exposed as a top-level property in a component’s template, Vue automatically unwraps it, so template expressions usually use count rather than count.value. JavaScript code still uses .value.
#1 Best Overall
Use reactive() for a stable object
reactive() returns a Proxy for an object-like value. Read and mutate its properties directly, which can feel natural when related fields form one stable object.
import { reactive } from 'vue'
const form = reactive({
name: '',
email: ''
})
form.name = 'Ari'
Vue tracks access through the proxy, not through the original raw object. Use the returned proxy consistently; mutating the original object does not trigger updates through the reactive connection. The Reactivity in Depth guide explains how Vue’s proxy-based tracking works.
Two reactive() pitfalls to avoid
Do not replace the whole proxy
Reactivity is tied to the original proxy reference. Assigning a new object to a variable that previously held the proxy does not transfer that connection to the replacement object. If the state needs whole-value replacement, use a ref and assign the new object to .value.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Do not destructure primitive properties when you need a live connection
For example, const { count } = state copies the current primitive value into a local binding. Reading or changing that binding no longer accesses the proxy’s count property, so it is disconnected from that property’s reactivity. A different case is an object-valued property: a local variable referring to that nested object can still observe reactive mutations to the object, even though the local binding itself is not a tracked property access.
When Vue unwraps refs—and when it does not
Ref unwrapping depends on where the ref is used. A top-level ref exposed to a template is unwrapped, and a ref used as a property of a deep reactive object is also unwrapped. That behavior does not apply in every context:
- A ref nested inside a plain object expression in a template may not be unwrapped like a top-level template ref.
- A ref stored in a reactive array or a native collection such as
Mapremains a ref; access its value with.value. - Properties of a shallow reactive object do not unwrap refs in the way deep reactive properties do.
const count = ref(0)
const state = reactive({ count })
const counts = reactive([count])
state.count // unwrapped value
counts[0].value // ref remains in the array
For details on these context-specific rules, see Vue’s Reactivity API: Core.
When to use shallowRef()
If another system owns an object’s state, or deep conversion is undesirable for a large structure, consider shallowRef(). It makes the ref’s value reactive without deeply converting the inner object. Replace .value when the external state changes so Vue can observe the ref update. Vue describes this pattern in Reactivity in Depth.
Free tools Windows power users keep installed
One-click scans. No signup required.
Decision cheat sheet
- Choose
ref()for primitives, replaceable values, and state passed through reusable functions when you need to preserve the ref connection. - Choose
reactive()when an object’s grouped shape is convenient and you can keep using its proxy without replacing it or destructuring primitive properties. - Choose
shallowRef()when you want to avoid deep conversion, particularly for externally managed state.
In short: start with ref(); reach for reactive() when direct access to a stable object makes the code clearer.
Quick Recap
Best Value
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




