🏪 Pinia State Management: Your App’s Memory Box
The Big Idea
Imagine you have a special toy box that every room in your house can see and use. No matter where you are—kitchen, bedroom, or living room—you can always find your favorite toys in that one box.
That’s exactly what Pinia does for your Vue.js app! It’s a shared memory box where all your components can store and find important information.
🎯 Why Do We Need Pinia?
Think about playing a video game with your friends. Everyone needs to know:
- How many coins you collected
- What level you’re on
- Which power-ups you have
Without a shared score board, each player would have different numbers! Chaos!
Pinia is that shared scoreboard. Everyone sees the same thing, always.
📦 Creating a Pinia Store
A store is like creating your special toy box. You give it a name and decide what goes inside.
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
name: 'My Counter'
}),
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment() {
this.count++
}
}
})
What Just Happened?
graph TD A["defineStore"] --> B["Give it a name: 'counter'"] B --> C["Add State: things to remember"] C --> D["Add Getters: smart calculations"] D --> E["Add Actions: things you can do"] E --> F["Your Store is Ready!"]
Key Parts:
defineStore→ Creates your box'counter'→ The box’s name (unique ID)state→ What’s inside the boxgetters→ Special ways to look at what’s insideactions→ Things you can do with the box
đź§ State in Pinia
State is the stuff your store remembers. Like your brain remembering your favorite color!
state: () => ({
// Numbers
score: 0,
// Text
playerName: 'Hero',
// Lists
inventory: ['sword', 'shield'],
// True/False
isGameOver: false
})
Why Is State a Function?
Good question! When you write state: () => ({...}), you’re creating a fresh copy for each time it’s used.
It’s like giving each player their own copy of the game, not sharing one save file!
🔍 Getters in Pinia
Getters are like smart calculators. They look at your state and figure things out automatically.
getters: {
// Simple getter
doubleScore: (state) => state.score * 2,
// Getter using another getter
quadrupleScore() {
return this.doubleScore * 2
},
// Getter with filtering
activeItems: (state) => {
return state.inventory.filter(item => item.isActive)
}
}
Real Example
Imagine a shopping cart:
getters: {
// Count items
itemCount: (state) => state.items.length,
// Calculate total price
totalPrice: (state) => {
return state.items.reduce(
(sum, item) => sum + item.price,
0
)
},
// Check if cart is empty
isEmpty: (state) => state.items.length === 0
}
Why Getters Are Amazing:
- They update automatically when state changes
- They’re cached (remembered) until data changes
- They keep calculations in one place
⚡ Actions in Pinia
Actions are the things you can DO. Like pressing buttons in a game!
actions: {
// Simple action
increment() {
this.count++
},
// Action with a value
addScore(points) {
this.score += points
},
// Async action (waiting for something)
async fetchUserData() {
const response = await fetch('/api/user')
this.userData = await response.json()
}
}
Actions vs Direct Changes
You could change state directly, but actions are better:
// ❌ Works, but not recommended
store.count = 5
// âś… Better! Use an action
store.setCount(5)
Why?
- Actions can do multiple things at once
- Actions can be async (wait for data)
- Actions are easier to track and debug
🎮 Using Pinia Store
Now let’s use our store in a Vue component!
Step 1: Import and Use
<script setup>
import { useCounterStore } from '@/stores/counter'
// Get the store
const counter = useCounterStore()
</script>
<template>
<!-- Read state -->
<p>Count: {{ counter.count }}</p>
<!-- Use getters -->
<p>Double: {{ counter.doubleCount }}</p>
<!-- Call actions -->
<button @click="counter.increment">
Add One!
</button>
</template>
Step 2: Destructuring (Be Careful!)
// ❌ This BREAKS reactivity!
const { count } = useCounterStore()
// âś… Use storeToRefs for reactive values
import { storeToRefs } from 'pinia'
const store = useCounterStore()
const { count, name } = storeToRefs(store)
// Actions can be destructured normally
const { increment } = store
graph TD A["Import Store"] --> B["Call useXxxStore"] B --> C{Need to destructure?} C -->|State/Getters| D["Use storeToRefs"] C -->|Actions| E["Destructure directly"] D --> F["Reactive values!"] E --> F
🔄 Store Reset
Sometimes you need to start fresh. Like pressing the reset button on a game!
The Magic $reset Method
const store = useCounterStore()
// Do some changes
store.count = 100
store.name = 'Changed!'
// Reset everything to initial values
store.$reset()
// Now count is 0, name is 'My Counter' again!
When To Reset?
- User logs out → Reset user store
- Game ends → Reset game store
- Form submits → Reset form store
- Testing → Reset between tests
Custom Reset
Sometimes you want to reset only some things:
actions: {
resetScore() {
this.score = 0
// Keep playerName unchanged
},
fullReset() {
this.$reset()
}
}
🌟 Putting It All Together
Here’s a complete, real-world example:
// stores/game.js
import { defineStore } from 'pinia'
export const useGameStore = defineStore('game', {
state: () => ({
player: 'Hero',
score: 0,
level: 1,
isPlaying: false
}),
getters: {
displayScore: (state) => {
return `Score: ${state.score} points`
},
canLevelUp: (state) => {
return state.score >= state.level * 100
}
},
actions: {
startGame() {
this.isPlaying = true
this.score = 0
this.level = 1
},
addPoints(points) {
this.score += points
if (this.canLevelUp) {
this.level++
}
},
endGame() {
this.isPlaying = false
},
newGame() {
this.$reset()
}
}
})
Using it in a component:
<script setup>
import { useGameStore } from '@/stores/game'
import { storeToRefs } from 'pinia'
const game = useGameStore()
const { player, displayScore, level } = storeToRefs(game)
</script>
<template>
<div>
<h1>{{ player }}'s Adventure</h1>
<p>{{ displayScore }}</p>
<p>Level: {{ level }}</p>
<button @click="game.addPoints(10)">
Collect Coin! +10
</button>
<button @click="game.newGame()">
Start Over
</button>
</div>
</template>
🎉 You Did It!
Now you understand Pinia! Let’s recap:
| Concept | What It Does | Analogy |
|---|---|---|
| Store | Container for shared data | Toy box everyone can use |
| State | The data itself | Toys in the box |
| Getters | Computed values | Calculator that auto-updates |
| Actions | Methods to change state | Buttons you can press |
| $reset | Return to initial state | Reset button |
Remember: Pinia makes sharing data between components easy and predictable. No more passing props through 10 levels of components!
🚀 You’re now ready to manage state like a pro!
