toReactive
Utility function to convert values and refs into reactive proxies with deep ref unwrapping.
Overview
Creates reactive versions of plain objects while automatically unwrapping nested refs. Supports Maps, Sets, arrays, and objects with full proxy support.
Key Features:
- Automatic ref unwrapping at all nesting levels
- Deep reactive proxying
- Map and Set support with ref unwrapping
- Nested object/array reactivity
- Type preservation
- Perfect for reactive state derived from refs
Signature
function toReactive<Z extends object>(
objectRef: MaybeRef<Z>,
): UnwrapNestedRefs<Z>
Parameters
The object or ref to convert. Can be a plain object, array, Map, or Set.
Return Value
Reactive proxy that automatically unwraps refs and maintains reactivity.
Basic Usage
Plain Objects
import { ref } from 'vue'
import { toReactive } from '@vuetify/v0'
// Convert ref to reactive
const state = ref({ name: 'John', age: 30 })
const rstate = toReactive(state)
console.log(rstate.name) // 'John' (no .value needed)
rstate.age = 31 // Updates original ref
// Convert plain object to reactive
const plain = { count: 0 }
const reactive = toReactive(plain)
reactive.count++ // Now reactive
Automatic Ref Unwrapping
import { ref } from 'vue'
import { toReactive } from '@vuetify/v0'
const nameRef = ref('Alice')
const ageRef = ref(25)
const userRef = ref({
name: nameRef,
age: ageRef,
})
const user = toReactive(userRef)
// Refs are automatically unwrapped
console.log(user.name) // 'Alice' (not ref)
console.log(user.age) // 25
// Setting values updates underlying refs
user.name = 'Bob'
console.log(nameRef.value) // 'Bob'
Arrays
import { ref } from 'vue'
import { toReactive } from '@vuetify/v0'
const items = ref([1, 2, 3])
const reactiveItems = toReactive(items)
reactiveItems.push(4)
console.log(items.value) // [1, 2, 3, 4]
reactiveItems[0] = 10
console.log(items.value[0]) // 10
Advanced Usage
Map Collections
import { ref } from 'vue'
import { toReactive } from '@vuetify/v0'
const mapRef = ref(new Map([
['key1', 'value1'],
['key2', ref('value2')],
]))
const reactiveMap = toReactive(mapRef)
// Automatically unwraps ref values
console.log(reactiveMap.get('key1')) // 'value1'
console.log(reactiveMap.get('key2')) // 'value2' (unwrapped)
// Setting values
reactiveMap.set('key3', 'value3')
console.log(mapRef.value.get('key3')) // 'value3'
// Updating ref values
reactiveMap.set('key2', 'updated')
console.log(reactiveMap.get('key2')) // 'updated'
// Iteration with unwrapping
for (const [key, value] of reactiveMap) {
console.log(key, value) // Values are unwrapped
}
Array.from(reactiveMap.values()) // All values unwrapped
Set Collections
import { ref } from 'vue'
import { toReactive } from '@vuetify/v0'
const setRef = ref(new Set([ref('a'), ref('b')]))
const reactiveSet = toReactive(setRef)
// Iteration unwraps refs
for (const value of reactiveSet) {
console.log(value) // 'a', 'b' (unwrapped)
}
reactiveSet.add('c')
console.log(setRef.value.has('c')) // true
Nested Structures
import { ref } from 'vue'
import { toReactive } from '@vuetify/v0'
const data = ref({
user: {
name: ref('John'),
profile: {
bio: ref('Developer'),
age: ref(30),
},
},
items: [ref(1), ref(2), ref(3)],
})
const reactive = toReactive(data)
// All nested refs are unwrapped
console.log(reactive.user.name) // 'John'
console.log(reactive.user.profile.bio) // 'Developer'
console.log(reactive.items[0]) // 1
// Updates propagate to original refs
reactive.user.profile.age = 31
console.log(data.value.user.profile.age.value) // 31
Dynamic Updates
import { ref, watchEffect } from 'vue'
import { toReactive } from '@vuetify/v0'
const stateRef = ref({ count: 0 })
const state = toReactive(stateRef)
watchEffect(() => {
console.log('Count:', state.count)
})
// Changing ref triggers watchers
stateRef.value = { count: 5 } // Logs: Count: 5
// Changing proxy also triggers watchers
state.count = 10 // Logs: Count: 10
Type Safety
import { ref } from 'vue'
import { toReactive } from '@vuetify/v0'
import type { UnwrapNestedRefs } from 'vue'
interface User {
name: string
age: number
settings: {
theme: string
notifications: boolean
}
}
const userRef = ref<User>({
name: 'Alice',
age: 25,
settings: {
theme: 'dark',
notifications: true,
},
})
// Type is preserved and unwrapped
const user: UnwrapNestedRefs<User> = toReactive(userRef)
user.name = 'Bob' // Type-safe
user.settings.theme = 'light' // Nested properties type-safe
Use Cases
Component State
<script setup lang="ts">
import { ref } from 'vue'
import { toReactive } from '@vuetify/v0'
const propsRef = ref({
title: 'Hello',
count: 0,
enabled: true,
})
const props = toReactive(propsRef)
function increment() {
props.count++
}
</script>
<template>
<div>
<h1>{{ props.title }}</h1>
<p>Count: {{ props.count }}</p>
<button @click="increment" :disabled="!props.enabled">
Increment
</button>
</div>
</template>
Composable Return Values
import { ref } from 'vue'
import { toReactive } from '@vuetify/v0'
export function useCounter(initial = 0) {
const state = ref({
count: initial,
double: computed(() => state.value.count * 2),
})
const reactive = toReactive(state)
function increment() {
reactive.count++
}
function decrement() {
reactive.count--
}
return {
state: reactive,
increment,
decrement,
}
}
// Usage - no .value needed
const { state, increment } = useCounter()
console.log(state.count) // 0
increment()
console.log(state.count) // 1
Notes
- For plain objects, returns
reactive(object) directly
- For refs, creates a proxy that unwraps nested refs automatically
- Maps and Sets get specialized proxies for proper collection behavior
- All operations maintain reactivity through Vue’s reactive system
- Property descriptors are preserved (with
configurable: true override)
- Works with circular references and complex nested structures