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/sfc-script-setup-reactivity.md
1---2title: Variables in Script Setup Are Not Reactive by Default3impact: HIGH4impactDescription: Forgetting to wrap variables with ref() or reactive() causes silent reactivity failures in script setup5type: gotcha6tags: [vue3, sfc, script-setup, reactivity, ref, composition-api]7---89# Variables in Script Setup Are Not Reactive by Default1011**Impact: HIGH** - Unlike Options API's `data()` which automatically makes properties reactive, variables declared in `<script setup>` are plain JavaScript values. You must explicitly use `ref()` or `reactive()` to make them reactive. Forgetting this causes the UI to not update when values change.1213## Task Checklist1415- [ ] Always wrap primitive values (strings, numbers, booleans) with `ref()`16- [ ] Use `reactive()` for objects when you don't need to reassign the whole object17- [ ] Remember to access `.value` on refs in script (not needed in templates)18- [ ] Use `computed()` from Vue, not a plain function, for derived reactive state1920**Problematic Code:**21```vue22<script setup>23// BAD: These are NOT reactive!24let count = 025let message = 'Hello'26let user = { name: 'John', age: 30 }2728function increment() {29count++ // This change won't update the UI!30}3132function updateMessage() {33message = 'World' // UI won't reflect this change!34}35</script>3637<template>38<div>39<!-- Will always show initial values -->40<p>Count: {{ count }}</p>41<p>Message: {{ message }}</p>42<button @click="increment">Increment</button>43<button @click="updateMessage">Update</button>44</div>45</template>46```4748**Correct Code:**49```vue50<script setup>51import { ref, reactive, computed } from 'vue'5253// GOOD: Primitives wrapped with ref()54const count = ref(0)55const message = ref('Hello')5657// GOOD: Object with reactive()58const user = reactive({ name: 'John', age: 30 })5960// GOOD: Computed for derived state61const doubleCount = computed(() => count.value * 2)6263function increment() {64count.value++ // Use .value for refs in script65}6667function updateMessage() {68message.value = 'World'69}7071function updateUser() {72user.name = 'Jane' // No .value needed for reactive objects73}74</script>7576<template>77<div>78<!-- No .value needed in templates - Vue unwraps automatically -->79<p>Count: {{ count }}</p>80<p>Double: {{ doubleCount }}</p>81<p>Message: {{ message }}</p>82<p>User: {{ user.name }}</p>83<button @click="increment">Increment</button>84</div>85</template>86```8788## Common Mistake: Plain Computed8990```vue91<script setup>92import { ref } from 'vue'9394const items = ref([1, 2, 3, 4, 5])9596// BAD: Plain function, not reactive - won't update when items change97const total = items.value.reduce((sum, n) => sum + n, 0)9899// BAD: Arrow function - recalculates but Vue doesn't track it100const getTotal = () => items.value.reduce((sum, n) => sum + n, 0)101</script>102103<template>104<!-- total never updates, getTotal works but isn't optimal -->105<p>Total: {{ total }}</p>106</template>107```108109```vue110<script setup>111import { ref, computed } from 'vue'112113const items = ref([1, 2, 3, 4, 5])114115// GOOD: computed() tracks dependencies and caches result116const total = computed(() => items.value.reduce((sum, n) => sum + n, 0))117</script>118119<template>120<p>Total: {{ total }}</p> <!-- Updates when items change -->121</template>122```123124## When to Use ref() vs reactive()125126```vue127<script setup>128import { ref, reactive } from 'vue'129130// Use ref() for:131// - Primitives (string, number, boolean)132// - Values you might reassign entirely133const count = ref(0)134const isLoading = ref(false)135const selectedId = ref<number | null>(null)136137// Use reactive() for:138// - Objects/arrays you'll mutate but not reassign139// - When you want to avoid .value140const form = reactive({141name: '',142email: '',143errors: []144})145146// Gotcha: Can't reassign reactive objects147const user = reactive({ name: 'John' })148// user = { name: 'Jane' } // This breaks reactivity!149// user.name = 'Jane' // This works150151// Use ref() if you need to reassign objects152const userData = ref({ name: 'John' })153userData.value = { name: 'Jane' } // This works154</script>155```156157## Template Automatic Unwrapping158159Vue automatically unwraps refs in templates:160161```vue162<script setup>163import { ref } from 'vue'164165const count = ref(0)166const user = ref({ name: 'John' })167</script>168169<template>170<!-- All of these work - no .value needed -->171<p>{{ count }}</p>172<p>{{ user.name }}</p>173<input v-model="count" type="number">174<button @click="count++">Increment</button>175</template>176```177178But in event handlers written inline, you might still need `.value`:179180```vue181<template>182<!-- This works (Vue handles it) -->183<button @click="count++">+1</button>184185<!-- For complex logic, .value may be needed -->186<button @click="() => { count.value = Math.max(0, count.value - 1) }">187-1 (min 0)188</button>189</template>190```191192## Reference193- [Vue.js Reactivity Fundamentals](https://vuejs.org/guide/essentials/reactivity-fundamentals.html)194- [Vue.js ref()](https://vuejs.org/api/reactivity-core.html#ref)195- [Vue.js reactive()](https://vuejs.org/api/reactivity-core.html#reactive)196