Skip to content
wannacode

State

In short: State is a value a component remembers between renders; in Vue it is kept with ref, and every change updates the component on the page.

Problem

The button does not count

The product card has an "Add" button and an "In the cart" counter. Press "Add" three times.

Vue

The console says count = 1, 2, 3. The card says 0. There is no error. The variable count changes, but the screen does not follow it. Where is the difference?

The idea

A plain variable is not enough

The card is not drawn by your code but by the library. It draws again only when it knows that something changed. count++ changes the variable, but tells nobody.

<script setup> runs once for every card, and the template is run by Vue. Vue runs the template again only when something it watches changes. Vue does not watch a plain let variable. The click changes count, but the template does not run again, so the card stays at 0.

You need data that the component remembers between two renders and whose change draws it again. This is called state.

The idea

useState and ref

<script setup>
import { ref } from 'vue';
const count = ref(0);
function add() {
count.value++;
}
</script>
<template>
<p>In the cart: {{ count }}</p>
</template>
  • ref(0) returns a box, and the number is in count.value. In the script you write .value.
  • In the template Vue unwraps the box: you write {{ count }}, without .value.
  • When the template reads count, Vue remembers it. When count.value changes, Vue runs the template again.

The click handler is given as a function: @click="add". In Vue @click="add()" or @click="count++" also work: the template wraps them in a function that runs on the click.

Vue

See it

See what happens on a click

<script setup>
const count = ref(0);
function addThree() {
count.value++;
count.value++;
count.value++;
}
</script>
<template>
<article>
<button @click="addThree">+3</button>
<p>In the cart: {{ count }}</p>
</article>
</template>
Step by step, in words
  1. The card keeps count in a ref: a box whose value is count.value. Vue watches who reads it.
  2. Vue runs the template. It reads count and returns a description, a button and a text with 0. Vue remembers that the template reads count.
  3. The first time, the description becomes real elements in the DOM. The page now shows “In the cart: 0”.
  4. A click on +3: the browser calls addThree.
  5. count.value++ three times. The box changes at once: 1, 2, 3. Vue plans to run the template again, but only once.
  6. The handler is done. Vue runs the template once, with count = 3, and it returns a new description.
  7. Vue compares the new description with the old one. The button is the same, only the text changed. In the DOM only the text changes.
  8. The counter shows 3. A change of a ref is immediate, and the drawing happens once, after the handler.

The card is not built again. The library compares the new description with the old one and changes only the difference in the DOM: the text. The button is the same element and the focus stays on it. That is the difference from render() in the DOM module, which replaced everything.

Try it

Fix the counter

This is the card from the start. Make count state, so that after every click on "Add" the card shows the new number.

Vue

The idea

The box and the number

Mineral water is also sold in a pack of 6 bottles. It is handy for the pack button to call add() six times. Here the two libraries behave differently.

count.value is always the current number, so count.value++ six times gives 6. The template does not run six times for that. Vue collects the changes and runs it once, after the handler.

The mistake here is a different one. The box is count, and count.value is a number. A number put into another variable is a copy:

let bottles = count.value;
bottles += 6; // changes the copy, count stays the same

Vue watches the box, not every variable the number has been in. Write into the box: count.value += 6.

Try it

Find the bug

"Add a pack" should add 6 bottles. Which line is to blame?

Pick the line with the bug.

Try it

A pack of water

Write addBox() so that "Add a pack (6 pcs)" adds 6 bottles to what is already in the cart. The check presses "Add" once, then the pack twice, and expects 1, 7 and 13.

Vue

The idea

Each card remembers its own

State belongs to the instance, not to the code. The catalogue has two cards from the same ProductCard, and each has its own count:

Vue

Press "Add" on the bread twice and on the milk once: 2 and 1. The library keeps the state of each card by its place in the tree of components. Remove the card from the page and its state goes with it. When two cards must show the same number, it has to live in their common parent. That is the lesson on shared state in this module.

More precisely, if you are curious

ref and reactive. reactive({ count: 0 }) makes the object watched and has no .value, but it works only with objects and the whole object cannot be replaced. ref works with any value, so the Vue documentation recommends it as the main one.

When the template runs. Vue runs the template again in the next microtask, after the code running now is done. await nextTick() waits for that render, for example to read the new DOM.

Unwrapping. In the template, refs at the top level of <script setup> are unwrapped. A ref inside a plain object ({ total: ref(0) }) is not unwrapped in an expression like {{ cart.total + 1 }}.

Options API. Older Vue code writes the component as an object with data(), methods and this.count. New code uses <script setup> and the Composition API.

Exercise

Choose the quantity

The catalogue has three cards. In each one add a quantity that starts at 1, a "−" button (name for the screen reader: "Fewer White bread"), a "+" button ("More White bread") and a line "2 × €1.20 = €2.40" with the quantity, the price and the total. "−" is disabled when the quantity is 1. Each card counts on its own.

Vue

Test

Check yourself

1. Why do let count = 0 and count++ in the component not change the screen?
2. Where do you write .value for const count = ref(0)?
3. count.value is 0. The handler does count.value++ three times. What happens?
4. What does let bottles = count.value; bottles += 6; do?
5. The page has two ProductCards, each with its own count. The customer presses "Add" in the first one. What happens?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·