Vue.js course Β· Module 11: Pinia

State in Pinia

2 min read
In this lesson5

State is reactive data stored in the store - like the main computer's database at NOVA LAB. Every sensor reading, every mission parameter is recorded here and available to all station modules.

Defining State

In Composition API style, state is simply ref() and reactive():

1import { defineStore } from 'pinia'
2import { ref, reactive } from 'vue'
3
4export const useResourceStore = defineStore('resources', () => {
5  // Simple values - ref()
6  const oxygen = ref(98.5)
7  const water = ref(1250)
8  const energy = ref(87)
9  const isEmergency = ref(false)
10
11  // Objects - reactive() or ref()
12  const crew = reactive({
13    commander: 'Nova',
14    engineer: 'Atlas',
15    scientist: 'Darwin'
16  })
17
18  // Arrays
19  const logs = ref([])
20
21  return { oxygen, water, energy, isEmergency, crew, logs }
22})

Modifying State

State can be modified directly in actions or from a component:

1export const useResourceStore = defineStore('resources', () => {
2  const oxygen = ref(98.5)
3
4  // Action modifies state
5  function consumeOxygen(amount) {
6    oxygen.value = Math.max(0, oxygen.value - amount)
7  }
8
9  return { oxygen, consumeOxygen }
10})
1<script setup>
2const store = useResourceStore()
3
4// Direct modification (works, but actions are better)
5store.oxygen = 95.0
6
7// Through action (recommended)
8store.consumeOxygen(3.5)
9</script>

$patch - Updating Multiple Properties

$patch allows updating multiple state properties at once - more efficient than modifying one at a time:

1const store = useResourceStore()
2
3// Object patch - simple override
4store.$patch({
5  oxygen: 95.0,
6  water: 1200,
7  energy: 82,
8  isEmergency: false
9})
10
11// Function patch - for complex operations
12store.$patch((state) => {
13  state.oxygen -= 3.5
14  state.logs.push({
15    type: 'consumption',
16    timestamp: Date.now()
17  })
18})

$reset - Restoring Initial State

In Options API style $reset() works automatically. In Composition API you must define your own reset function:

1export const useResourceStore = defineStore('resources', () => {
2  const oxygen = ref(100)
3  const water = ref(1500)
4  const energy = ref(100)
5
6  // Custom reset for Composition API
7  function $reset() {
8    oxygen.value = 100
9    water.value = 1500
10    energy.value = 100
11  }
12
13  return { oxygen, water, energy, $reset }
14})
15
16// Usage
17const store = useResourceStore()
18store.$reset() // Restores initial values

$state - Accessing the Entire State

1const store = useResourceStore()
2
3// Read entire state
4const snapshot = store.$state
5
6// Override entire state
7store.$state = {
8  oxygen: 100,
9  water: 1500,
10  energy: 100,
11  isEmergency: false,
12  crew: { commander: 'Nova' },
13  logs: []
14}
Code for this lesson: App.vue
1<script setup>
2import { ref, computed } from 'vue'
3
4// Resource Store simulation
5const oxygen = ref(98.5)
6const water = ref(1250)
7const energy = ref(87)
8const logs = ref([])
9
10// Getters
11const oxygenStatus = computed(() => oxygen.value > 50 ? 'OK' : 'LOW')
12const waterStatus = computed(() => water.value > 500 ? 'OK' : 'LOW')
13const energyStatus = computed(() => energy.value > 40 ? 'OK' : 'LOW')
14
15// Actions
16function consumeOxygen(amount) {
17  oxygen.value = Math.max(0, +(oxygen.value - amount).toFixed(1))
18  addLog('Oxygen consumed: -' + amount + '%')
19}
20
21function consumeWater(amount) {
22  water.value = Math.max(0, water.value - amount)
23  addLog('Water consumed: -' + amount + 'L')
24}
25
26function consumeEnergy(amount) {
27  energy.value = Math.max(0, energy.value - amount)
28  addLog('Energy consumed: -' + amount + '%')
29}
30
31function addLog(message) {
32  logs.value.unshift({
33    message,
34    time: new Date().toLocaleTimeString()
35  })
36  if (logs.value.length > 8) logs.value.pop()
37}
38
39// $patch simulation
40function patchResources(updates) {
41  if (updates.oxygen !== undefined) oxygen.value = updates.oxygen
42  if (updates.water !== undefined) water.value = updates.water
43  if (updates.energy !== undefined) energy.value = updates.energy
44  addLog('Resources patched')
45}
46
47function resetResources() {
48  oxygen.value = 100
49  water.value = 1500
50  energy.value = 100
51  logs.value = []
52}
53</script>
54
55<template>
56  <div class="nova-lab">
57    <h1>NOVA LAB - State Management</h1>
58
59    <div class="grid">
60      <div class="resource-panel">
61        <h2>Resources</h2>
62        <div class="resource" v-for="r in [
63          { name: 'Oxygen', value: oxygen, unit: '%', status: oxygenStatus, action: () => consumeOxygen(5) },
64          { name: 'Water', value: water, unit: 'L', status: waterStatus, action: () => consumeWater(50) },
65          { name: 'Energy', value: energy, unit: '%', status: energyStatus, action: () => consumeEnergy(10) }
66        ]" :key="r.name">
67          <div class="r-header">
68            <span>{{ r.name }}</span>
69            <span :class="r.status.toLowerCase()">{{ r.status }}</span>
70          </div>
71          <div class="r-value">{{ r.value }}{{ r.unit }}</div>
72          <button @click="r.action" class="btn-sm">Consume</button>
73        </div>
74
75        <div class="btn-group">
76          <button @click="patchResources({ oxygen: 50, water: 800 })" class="btn">
77            $patch (O2=50, H2O=800)
78          </button>
79          <button @click="resetResources" class="btn btn-reset">
80            $reset
81          </button>
82        </div>
83      </div>
84
85      <div class="log-panel">
86        <h2>Activity Log</h2>
87        <div v-for="(log, i) in logs" :key="i" class="log-entry">
88          <span class="log-time">{{ log.time }}</span>
89          {{ log.message }}
90        </div>
91        <p v-if="!logs.length" class="empty">No activity yet...</p>
92      </div>
93    </div>
94  </div>
95</template>
96
97<style scoped>
98.nova-lab {
99  background: #0a0e27;
100  color: #00ff88;
101  min-height: 100vh;
102  padding: 20px;
103  font-family: monospace;
104}
105h1 { color: #00ff88; margin-bottom: 20px; text-align: center; }
106h2 { color: #00b4d8; margin-bottom: 15px; }
107.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; max-width: 700px; margin: 0 auto; }
108.resource-panel, .log-panel {
109  background: rgba(0, 180, 216, 0.1);
110  border: 2px solid #00b4d8;
111  padding: 15px;
112}
113.resource {
114  background: rgba(0, 255, 136, 0.05);
115  border: 1px solid #00ff88;
116  padding: 10px;
117  margin-bottom: 8px;
118}
119.r-header { display: flex; justify-content: space-between; margin-bottom: 5px; }
120.r-value { font-size: 1.2em; font-weight: bold; margin-bottom: 5px; }
121.ok { color: #00ff88; }
122.low { color: #ff4444; }
123.btn-sm {
124  background: #00b4d8;
125  color: #0a0e27;
126  border: none;
127  padding: 4px 12px;
128  cursor: pointer;
129  font-family: monospace;
130}
131.btn-group { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
132.btn {
133  background: #00b4d8;
134  color: #0a0e27;
135  border: none;
136  padding: 8px 12px;
137  cursor: pointer;
138  font-family: monospace;
139  font-size: 0.85em;
140}
141.btn:hover { background: #00ff88; }
142.btn-reset { background: #ff9900; }
143.log-entry {
144  padding: 5px 0;
145  border-bottom: 1px solid rgba(0, 180, 216, 0.3);
146  font-size: 0.85em;
147}
148.log-time { color: #00b4d8; margin-right: 8px; }
149.empty { color: #666; font-style: italic; }
150</style>

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. How is state defined in a Pinia store (Options API)?

  2. 2. What does the $patch method do in Pinia?

These are 2 of 3 questions for this lesson. Solve the rest in the game.

Hands-on tasks in the game

  • Code editor

    Practice working with state and the $patch method

  • Vertical ordering

    Arrange the steps for configuring Pinia in a Vue application

  • Click in order

    Arrange the elements to correctly use storeToRefs

  • Code editor

    Practice using $patch with a callback function

Useful articles