toRef() and toRefs(): the Bridge Between reactive() and ref()
AI generated
<v/>
{ }
Vue.js · Reactivity · Composition API · Composables
toRef() and toRefs()
the bridge between reactive() and ref()

A plain destructuring assignment breaks the reactivity of a reactive() object, because individual fields become plain copies. toRef() and toRefs() solve exactly this problem by turning each property into a standalone ref that stays coupled to the original source.

17 min read toRef · toRefs · composables · destructuring Vue 3.0 - 3.3+ · getter refs

1. Why reactive() and ref() can talk past each other

An object created with reactive() is reactive as long as you access it through the object reference itself. As soon as a single property gets destructured, for example with const { count } = state, you end up with a plain, non reactive copy of the current value. Changes to state.count afterward have no effect whatsoever on the local variable count, a behavior that surprises many beginners because it matches how destructuring works for normal JavaScript objects, but not what you would expect from a reactive system.

This is exactly where toRef() and toRefs() come in. Both functions create a real ref() out of a property of a reactive() object, one that stays coupled to the original source: reading the ref reads the current value of the source, writing the ref writes back into the source. That coupling lets you pull individual fields out of a reactive object without severing the connection to the original data source.

The practical benefit shows up mainly in composables. A function that internally manages a reactive() object with several fields can use toRefs() to expose each individual field as a standalone ref, so consumers can destructure individual values without losing reactivity, a pattern found in nearly every public composable API from VueUse and similar libraries.

2. toRef() in detail: how the coupling works

toRef(source, key) creates a ref whose .value access is internally redirected to source[key]. Unlike a copy created by a destructuring assignment, this does not create a new, independent storage location for the value. Instead, the returned ref acts as a pure proxy onto the original property: reading .value reads live from source[key], writing writes back live.

That coupling works in both directions. If another component or function changes source.key directly, it is reflected immediately in the value of the ref created via toRef(), because both ultimately access the same underlying reactive property. Conversely, assigning to myRef.value also updates source.key, which is what clearly distinguishes toRef() from a simple one time copy.


import { reactive, toRef } from 'vue'

const state = reactive({ count: 0, name: 'Vue' })

// countRef stays coupled to state.count — it is NOT an independent copy
const countRef = toRef(state, 'count')

console.log(countRef.value) // 0

state.count = 5
console.log(countRef.value) // 5 — reflects the change to the source object

countRef.value = 10
console.log(state.count) // 10 — writing back through the ref updates the source

3. toRefs(): breaking an entire object into refs

toRefs(source) is the practical extension of toRef() for cases where you need not just one, but all properties of a reactive object as standalone, coupled refs. Instead of calling toRef(state, 'field') individually for every field, toRefs() creates a new object in a single call whose values are all refs, each coupled to the corresponding property of the source.

The decisive advantage shows up during destructuring: if the result of toRefs(state) gets destructured, every destructured variable stays reactive, because it is already a standalone ref, not a plain value. That is exactly what makes toRefs() the standard tool at the end of almost every composable function that works internally with reactive(), but wants to offer destructurable, individually reactive values to the outside.


import { reactive, toRefs } from 'vue'

const state = reactive({
  count: 0,
  name: 'Vue',
  isActive: true
})

// All three properties become individually coupled refs at once
const { count, name, isActive } = toRefs(state)

count.value++       // updates state.count too
state.name = 'Vue 3' // name.value reflects this change automatically

console.log(count.value, name.value, isActive.value) // 1 'Vue 3' true

4. Destructuring composable returns without losing reactivity

The most common practical case for toRefs() is the last line of a composable. If a reactive() object is used internally for the entire internal state, because that makes the implementation cleaner, you still need to make sure consumers can destructure individual fields on return, without losing reactivity. Without toRefs() at the end, const { x, y } = useMousePosition() would return two frozen values that never change again.

This convention is by now so established that it almost counts as a contract between composable author and consumer: a composable returning an object with several reactive values should pass that object through toRefs() at the return point, so the return value can be used both as a whole and safely destructured, whichever the consumer prefers.


import { reactive, toRefs, onMounted, onBeforeUnmount } from 'vue'

export function useMousePosition() {
  // Internally, a single reactive object is easier to reason about
  const state = reactive({ x: 0, y: 0 })

  function update(event) {
    state.x = event.clientX
    state.y = event.clientY
  }

  onMounted(() => window.addEventListener('mousemove', update))
  onBeforeUnmount(() => window.removeEventListener('mousemove', update))

  // toRefs() at the return point keeps x and y independently reactive
  return toRefs(state)
}

// In a component:
// const { x, y } = useMousePosition()
// x and y stay reactive, even though they were destructured

5. toRef() with a getter instead of a property

Since Vue 3.3, toRef() also accepts a getter function as its only parameter, instead of a source plus a property name. The result is a read only ref whose value is recomputed on every access through the getter function, similar to computed(), but without its internal caching. That is useful when you want to pass along a simple transformation of an existing value as a ref, without setting up a full computed() for it.

The difference from computed() is subtle but relevant: a getter based toRef() gets re evaluated on every read, while computed() caches the result and only recomputes when its dependencies actually change. For expensive computations, computed() therefore remains the better choice, for simple, cheap transformations the getter toRef() is a more compact alternative.


import { ref, toRef } from 'vue'

const celsius = ref(20)

// toRef() with a getter function (Vue 3.3+): read-only, re-evaluated on every read
const fahrenheit = toRef(() => celsius.value * 9 / 5 + 32)

console.log(fahrenheit.value) // 68

celsius.value = 25
console.log(fahrenheit.value) // 77 — re-evaluated from the getter, no caching

6. Ref unwrapping in templates vs. in script

A common point of confusion with toRef() and toRefs() is automatic ref unwrapping in templates. Inside the <template> block, you access a ref created with toRefs() directly without .value, because Vue automatically unwraps top level refs in templates. Inside <script setup>, on the other hand, .value is strictly required, since no automatic unwrapping happens there, except for refs that already live inside a reactive() object.

This asymmetry occasionally leads to confusion when developers use a value destructured with toRefs() without .value in the script part, because they are used to the automatic unwrapping from templates. The rule of thumb remains: never write .value in the template, always use .value in the script part outside of templates when dealing with a real ref, regardless of whether it came from ref(), toRef(), or toRefs().

7. Historical bug: toRef() on missing properties

Before Vue 3.3, there was a limitation that frequently caused confusion: toRef(source, key) for a property that did not yet exist on source at call time returned a ref that never became reactive, even if the property was added later. The reason was that Vue had to check whether the property existed at all when creating the ref, and returned a plain, uncoupled fallback ref on a negative result.

Since Vue 3.3, this behavior has been fixed: toRef() now also works for properties added to the source object later on, as long as the source itself is reactive. If you are migrating code from older Vue 3 versions, pay close attention to exactly this historical detail, especially with composables that conditionally add optional fields to a reactive() object only later.

8. toRef()/toRefs() as a fixed API convention in composables

Composable libraries like VueUse establish toRefs() as a fixed part of their public API convention: almost every function that returns several reactive values does so through an object prepared with toRefs(), so consumers can freely choose whether to keep the entire result object or destructure individual fields. Adopting this convention makes your own composables consistent with the wider Vue ecosystem and reduces surprises for teammates used to other composables.

An additional pattern: composables that return both internal reactive state and derived computed() values often combine toRefs() for the base state with a direct return of the computed() references, since computed() values are already refs by themselves and need no additional conversion.


import { reactive, computed, toRefs } from 'vue'

export function useShoppingCart() {
  const state = reactive({
    items: [],
    discountPercent: 0
  })

  // computed refs need no extra conversion — they are refs already
  const total = computed(() =>
    state.items.reduce((sum, item) => sum + item.price, 0) *
    (1 - state.discountPercent / 100)
  )

  function addItem(item) {
    state.items.push(item)
  }

  return {
    ...toRefs(state), // items and discountPercent stay reactive when destructured
    total,             // already a ref, returned as-is
    addItem
  }
}

9. toRef() vs. toRefs() vs. plain destructuring

The overview below summarizes which tool is the right choice for which use case.

Use case Plain destructuring toRef() / toRefs() Result
Pulling out a single field Reactivity is lost toRef(state, 'field') Field stays coupled to source
Destructuring a composable return Frozen values for consumers toRefs(state) at the return point Consumers can safely destructure
Simple transformation of a value Requires its own computed() toRef(() => ...) since Vue 3.3 More compact for cheap computations
Passing an object along as a whole No problem, the reference stays reactive Unnecessary detour toRef/toRefs only needed for decomposition
Expensive computation with caching No caching on every access computed() instead of getter toRef() Avoids repeated expensive computation

The rule of thumb: as long as a reactive object is passed along as a whole, no conversion is needed. As soon as individual fields need to be pulled out or destructured, toRef() for single properties and toRefs() for the entire object are the right tools to preserve the coupling to the original reactive source.

Mironsoft

Vue 3 composables and API design consulting

Is your reactivity disappearing when you destructure?

We review your composable APIs for clean toRef/toRefs conventions and fix silent reactivity losses before they turn into hard to find bugs.

API audit

Checking composable returns for missing toRefs()

Consistency refactoring

Unified toRef/toRefs conventions across your whole team

Team training

Teaching safe destructuring of reactive objects

10. Summary

toRef() and toRefs() solve a very concrete problem: a plain destructuring of a reactive() object breaks the reactivity of individual fields, because they become plain copies. toRef(source, key) creates a single ref that stays bidirectionally coupled to the source property, toRefs(source) does the same for all properties of an object at once, making it the standard tool at the end of nearly every composable that works internally with reactive().

Since Vue 3.3, toRef() additionally accepts a getter function for read only, uncached transformations, a compact alternative to computed() for cheap computations. The historical bug where toRef() did not work for properties that did not yet exist was fixed in the same version. Applying this convention consistently in your own composables avoids silent reactivity losses and keeps you consistent with the rest of the Vue ecosystem.

toRef() and toRefs() in Vue 3 — The Essentials at a Glance

Core principle

Creates refs that stay bidirectionally coupled to a property of a reactive() object.

toRefs()

Converts all properties of an object into coupled refs at once, standard at a composable's return point.

Getter toRef() (Vue 3.3+)

Read only ref from a getter function, uncached, more compact than computed() for simple cases.

Most common mistake

Directly destructuring a reactive() object without toRefs(), losing reactivity in the process.

11. FAQ: toRef() and toRefs()

1What does toRef() do?
Creates a ref that stays bidirectionally coupled to a property of the source object.
2toRef() vs. toRefs()?
toRef converts one property, toRefs converts all properties of an object at once.
3Why does destructuring lose reactivity?
Destructuring creates a plain copy of the current value, disconnected from the source.
4When toRefs() at composable end?
When returning a reactive() object with several fields that should stay destructurable.
5toRef() with getter since 3.3?
Read only ref, recomputed on every access, no caching like computed().
6Historical bug before 3.3?
toRef() on a missing property stayed permanently non reactive, fixed since 3.3.
7.value needed in template?
No, template auto-unwraps top level refs, script part always needs .value.
8Is toRef() the same as computed()?
No, property toRef is writable and coupled, getter toRef uncached, computed cached.
9Does VueUse use toRefs() consistently?
Yes, almost every public composable API follows this convention.
10Object as a whole without toRefs()?
Yes, without destructuring, reactivity stays fully intact even without toRef/toRefs.