Vue.js course Β· Module 11: Pinia
State in Pinia
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. How is state defined in a Pinia store (Options API)?
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