Reactive State

By Syncfusion®, Inc. · Last updated

What reactivity is, <script setup> and top-level bindings, ref() and .value, unwrapping in templates, reactive() and proxies, the limits of reactive() and the destructuring problem, ref vs reactive, reactive arrays, Map and Set, DOM update timing with nextTick(), and typing state in TypeScript.

Reactivity

  • Reactivity = when data changes, everything that uses it updates by itself.
  • A spreadsheet works like this. Change one cell, and the total cell changes too.
  • Vue brings this idea to web pages.
  • You change the state. Vue updates the screen.
  • But Vue only watches state you create in a special way.

Simple lesson: reactive state is data that Vue watches.

A Real-World Example

  • Meera's page shows how many plates are in the cart.
  • She tried a normal let variable. The number on screen never moved.
  • Then she used ref(). Now the screen follows every click.
  • Run both buttons and compare.
  • What happened: the plain variable did go up. The Console proves it.
  • But the screen still shows 0, because Vue does not watch plain variables.
  • The ref number updates on every click.

What You Will Learn

  1. How <script setup> gives values to the template.
  2. How to make state with ref().
  3. When you need .value, and when you do not.
  4. How to make state with reactive().
  5. The limits of reactive(), and the destructuring trap.
  6. When to choose ref or reactive.
  7. How arrays, Map and Set stay reactive.
  8. When the screen really updates, and nextTick().
  9. How to type state in TypeScript.

script setup

  • <script setup> is the code part of a component.
  • Every top-level name in it can be used in the template: variables, functions and imports.
  • Top-level = written directly in the script, not inside a function.
  • Imports work too. You can use an imported function straight in the template.

Simple lesson: if a name sits at the top of <script setup>, the template can use it.

ref()

  • ref(value) wraps a value in a small box that Vue watches.
  • The box has one property: .value.
  • In the script, read and change it with .value.
  • In the template, write the name only. Vue opens the box for you. This is called unwrapping.
  • Why const? The box never changes, only what is inside it. So const count = ref(0) and then count.value++ is correct.

Animation: a ref is a box holding a value. The script changes box.value from 0 to 1. Vue sees the change and the template shows 1

const orders = ref(0)

.value

0 1

orders.value++ Vue: "it changed!"

Orders: 0 Orders: 1

Script: orders.value. Template: just orders.

A ref is a box. Change what is inside with .value, and Vue updates the screen.

  • What happened: the script used .value every time.
  • The template used customer, plates and order.dish with no .value.
  • A ref can hold an object too. Changes deep inside it are watched.

Template unwrapping only works for top-level refs. A ref stored inside a plain object, such as const box = { count: ref(1) }, is not unwrapped in an expression like {{ box.count + 1 }}. Keep refs at the top level.

Simple lesson: ref() makes a watched box. Use .value in the script, not in the template.

reactive()

  • reactive(object) makes a whole object reactive.
  • There is no .value. You use the object's properties directly.
  • It works by wrapping the object in a proxy.
  • Proxy = a stand-in object that notices every read and every change.
  • Always change the object through the proxy that reactive() returns. Changing the original plain object is not noticed.

Limits of reactive()

  • It only works with objects, arrays, Map and Set. Not with a plain number or text.
  • You cannot replace the whole object. order = { ... } breaks the link with the screen.
  • Destructuring breaks reactivity. let { plates } = order copies the number out of the proxy.
  • After that, changing plates does not change order or the screen.
  • What happened: "Add to the copy" changed only the copy. The Console shows it.
  • "Add to the order" changed the real state, so the screen updated.

Need separate variables from a reactive object? Use toRefs(order). It turns each property into a ref that stays linked. You will meet it again in Advanced Reactivity.

Simple lesson: reactive() makes an object watched. Do not destructure it or replace it.

ref vs reactive

Pointref()reactive()
HoldsAny value: number, text, object, arrayObjects, arrays, Map, Set
In the scriptcount.valueorder.plates
In the templatecountorder.plates
Replace the whole valueYes: list.value = []No
Destructure safelyYesNo (use toRefs)
Official adviceUse as the main toolUse for grouped values, carefully
  • The Vue docs advise ref() as your main tool, because it has fewer traps.
  • This course uses ref() most of the time.

Simple lesson: when in doubt, use ref().

Arrays, Map and Set

  • Array methods such as push, splice and sort are watched.
  • Replacing the array with list.value = [...] is watched too.
  • A reactive Map and a reactive Set are watched as well.
  • Set keeps only unique values. Adding "spicy" twice keeps one "spicy".

Simple lesson: arrays, Maps and Sets stay reactive when you change them with their own methods.

When the Screen Updates

  • Vue does not update the screen at the exact moment you change state.
  • It waits until your function finishes, then updates once.
  • This saves work when you change many values in a row.
  • To read the screen after the update, await nextTick().
  • What happened: right after the change, the page still said "Closed".
  • After await nextTick(), the page said "Open".

Simple lesson: Vue updates the screen a moment later, all at once. nextTick() waits for it.

Typing State in TypeScript

  • ref usually guesses the type from the starting value: ref(0) is a number.
  • Give the type yourself when the guess is not enough: ref<string | null>(null).
  • For a list of objects, describe one object with an interface.
  • reactive takes its type from the object, or from an interface you give it.
  • In the JavaScript flavour, the types are simply removed.

Real projects check these types with vue-tsc when you build. In this lab, types are removed before the code runs, and the editor does not check types in .vue files. So a wrong type does not stop the preview. Run vue-tsc in a real project to catch it.

Simple lesson: add types with ref<Type>() and interfaces. JavaScript just leaves them out.

TL;DR

  • Reactivity = when state changes, the screen updates by itself.
  • <script setup> = every top-level name is usable in the template.
  • ref(x) = a watched box. Use .value in the script, not in the template.
  • reactive(obj) = a watched object (a proxy). No .value.
  • Destructuring trap = let { a } = reactiveObj copies the value and loses reactivity.
  • ref vs reactive = prefer ref(). It can be replaced and destructured safely.
  • Arrays, Map, Set = stay reactive when changed with their own methods.
  • nextTick() = waits until Vue has updated the screen.
  • Typing = ref<Dish[]>([]), ref<Dish | null>(null).

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
Plain let for stateAny variable updates the screenUse ref() or reactive()The value changes, the screen does not
No .value in the scriptcount++ works on a refWrite count.value++Nothing happens, or a type error
Destructuring reactive()let { plates } = order stays linkedUse order.plates or toRefsChanges do not reach the screen
Replacing a reactive objectorder = { ... } is fineChange its properties, or use a refThe screen keeps the old object
reactive(0)reactive works with any valueUse ref(0) for single valuesA warning, and nothing is reactive
Reading the DOM right after a changeThe screen updates at onceawait nextTick() firstYou read the old text
A ref inside a plain object in {{ }}All refs are unwrappedKeep refs at the top levelYou see [object Object] or wrong maths

Check Yourself

Exercises

What to Remember

  • Only ref() and reactive() state updates the screen.
  • Refs use .value in the script. The template unwraps them.
  • Never destructure or replace a reactive() object.
  • Prefer ref(). It works for every kind of value.
  • Vue updates the screen after your code finishes. nextTick() waits for it.

Next: Computed Properties and Watchers