Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Vue 3 debugging reference for reactivity issues, computed errors, watcher bugs, async failures, and SSR hydration problems.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
reference/template-ref-unwrapping-top-level.md
1---2title: Template Ref Unwrapping Only Works for Top-Level Properties3impact: MEDIUM4impactDescription: Nested refs in template expressions render as [object Object] instead of their values5type: capability6tags: [vue3, reactivity, ref, template, unwrapping]7---89# Template Ref Unwrapping Only Works for Top-Level Properties1011**Impact: MEDIUM** - Vue only auto-unwraps refs that are top-level properties in the template render context. Nested refs (refs inside objects) are NOT unwrapped in expressions, causing `[object Object]` rendering or calculation errors.1213This caveat trips up developers when they store refs inside reactive objects or plain objects and try to use them in template expressions like `{{ object.count + 1 }}`.1415## Task Checklist1617- [ ] Keep refs at the top level of your setup return or script setup18- [ ] Destructure nested refs to top-level variables before using in expressions19- [ ] Be aware that text interpolation `{{ object.ref }}` DOES unwrap, but expressions `{{ object.ref + 1 }}` do NOT20- [ ] Consider restructuring data to avoid nested refs in templates2122**Incorrect:**23```vue24<script setup>25import { ref } from 'vue'2627const count = ref(0)28const object = { id: ref(1) }29</script>3031<template>32<!-- WRONG: Nested ref in expression - does NOT unwrap -->33<p>ID + 1 = {{ object.id + 1 }}</p>34<!-- Renders: "ID + 1 = [object Object]1" -->3536<!-- Surprisingly, plain interpolation DOES work -->37<p>ID = {{ object.id }}</p>38<!-- Renders: "ID = 1" (unwrapped because it's the final expression) -->39</template>40```4142**Correct:**43```vue44<script setup>45import { ref } from 'vue'4647const count = ref(0)48const object = { id: ref(1) }4950// SOLUTION 1: Destructure to top-level51const { id } = object52</script>5354<template>55<!-- CORRECT: Top-level ref unwraps in all expressions -->56<p>Count + 1 = {{ count + 1 }}</p>57<!-- Renders: "Count + 1 = 1" -->5859<!-- CORRECT: Destructured ref is now top-level -->60<p>ID + 1 = {{ id + 1 }}</p>61<!-- Renders: "ID + 1 = 2" -->62</template>63```6465```vue66<script setup>67import { ref, computed } from 'vue'6869const object = { id: ref(1) }7071// SOLUTION 2: Use computed for derived values72const idPlusOne = computed(() => object.id.value + 1)73</script>7475<template>76<!-- CORRECT: Computed handles the .value access -->77<p>ID + 1 = {{ idPlusOne }}</p>78</template>79```8081```vue82<script setup>83import { reactive } from 'vue'8485// SOLUTION 3: Use reactive object instead (refs inside reactive auto-unwrap)86const object = reactive({ id: 1 })87</script>8889<template>90<!-- CORRECT: Plain reactive property works in expressions -->91<p>ID + 1 = {{ object.id + 1 }}</p>92</template>93```9495```javascript96// WHY this happens:97// - Template compilation only adds .value to top-level identifiers98// - {{ count + 1 }} compiles to: count.value + 199// - {{ object.id + 1 }} compiles to: object.id + 1 (no .value added!)100// - Plain {{ object.id }} has special handling for display purposes101```102103## Reference104- [Vue.js Reactivity Fundamentals - Caveat when Unwrapping in Templates](https://vuejs.org/guide/essentials/reactivity-fundamentals.html#caveat-when-unwrapping-in-templates)105