Template Interpolation

Chapter 2
Template, Interpolation, and ref Reactivity
How Vue tracks changing values — and why a counter built with a plain variable wouldn't update

Chapter 1's interpolated values were constants that never changed. The moment a value needs to change and have the UI update, it must be reactive — Vue's tracking system that re-renders the template when a value changes. In the Composition API, the core tool for that is ref.

ref — A Reactive Value

<script setup> import { ref } from 'vue'; const count = ref(0); function increment() { count.value++; } </script> <template> <p>Count: {{ count }}</p> <button @click="increment">+1</button> </template>

ref(0) creates a reactive container holding 0. The catch that trips up everyone at first: in the <script>, you read and write the value through .value (count.value++) — but in the <template>, Vue unwraps it automatically, so it's just {{ count }}, no .value. This is Vue's equivalent of React's useState, but with two key differences: there's no separate setter function (you assign to .value directly), and the same ref is used both to read and to update.

.value in script, no .value in template
This split is the single most common Vue stumbling point. Inside <script setup>, always use count.value. Inside <template>, always use plain count — Vue unwraps it for you there. Forgetting .value in the script (writing count++) silently fails to update anything; adding .value in the template ({{ count.value }}) shows undefined.

Why Not a Plain Variable?

<!-- this does NOT work --> <script setup> let count = 0; function increment() { count++; // changes the variable, but Vue has no idea — the UI never updates } </script>

A plain let count = 0 increments fine in memory, but Vue isn't tracking it — nothing tells the framework the UI needs re-rendering, so the displayed number never changes. Exactly the same trap as a plain variable in React (Fundamentals Chapter 3). ref is what makes the value reactive; the change to .value is what Vue detects.

reactive — For Objects

import { reactive } from 'vue'; const state = reactive({ name: 'Philip', age: 35 }); function birthday() { state.age++; // no .value needed — reactive objects are accessed directly }

reactive is an alternative for objects, making the whole object reactive with no .value — properties are accessed directly (state.age++). In practice, many Vue developers use ref for everything (it works for objects too, via ref({...}).value) to avoid juggling two mental models, but reactive is common and worth recognizing. The trade-off: reactive only works on objects/arrays, and you lose reactivity if you destructure it.

Expressions in the Template

<p>{{ count * 2 }}</p> <p>{{ name.toUpperCase() }}</p> <p>{{ isActive ? 'On' : 'Off' }}</p>

Interpolation isn't limited to a bare value — any single JavaScript expression works inside {{ }}: arithmetic, method calls, ternaries. The same as React's curly braces, and like Angular's interpolation, it's expressions only (no statements like if or for — those have their own directives, next chapter).

React useStateVue ref
Createconst [c, setC] = useState(0)const c = ref(0)
Read (script)cc.value
Read (markup){c}{{ c }}
UpdatesetC(c + 1)c.value++

Coding Challenges

Challenge 1

Build a counter with a ref starting at 0 and a +1 button that increments it (remembering .value in the function), displaying the count via interpolation.

📄 View solution
Challenge 2

Build a component with a reactive object (via reactive) holding firstName and lastName, plus a button that changes one of them, displaying "Full name: [first] [last]" with both updating live.

📄 View solution
Challenge 3

Build a component with a price ref and a quantity ref, displaying their product directly in the template as a {{ }} expression (price * quantity), with buttons to change each — confirming the total recomputes automatically.

📄 View solution

Chapter 2 Quick Reference

  • ref(value) — a reactive value; Vue's equivalent of useState
  • Read/write with .value in <script>; use the plain name (auto-unwrapped) in <template>
  • No separate setter — assign to .value directly (count.value++)
  • A plain variable won't trigger UI updates — only a ref (or reactive) is tracked
  • reactive({...}) — makes an object reactive with no .value; objects/arrays only
  • {{ }} accepts any single JS expression (math, method calls, ternaries)
  • Next chapter: directives — v-bind, v-on, v-model