Vue 3 Pinia State Management
frontend
TypeScript
architecture
zen
Modern state management in Vue 3 using Pinia with TypeScript and composition API.
By emily_r
12/8/2025
Prompt
Vue 3 Pinia State Management
Implement global state management for [Application] using Pinia with TypeScript and Composition API.
Requirements
1. Store Structure
Create Pinia stores for:
- [Store 1] - [Data type] (e.g., User/Auth store)
- [Store 2] - [Data type] (e.g., Products/Cart store)
- [Store 3] - [Data type] (e.g., UI state store)
2. Store Features
Implement for each store:
- State - Reactive data properties
- Getters - Computed derived values
- Actions - Business logic and async operations
- TypeScript types - Full type safety
3. State Operations
Handle:
- Reading state values
- Updating state
- Async data fetching
- Resetting state
- State persistence (if needed)
4. Store Organization
Structure:
src/stores/
├── [domain].ts (e.g., user.ts)
├── [domain].ts (e.g., products.ts)
└── index.ts (exports)
5. Integration
Set up:
- Pinia plugin installation
- DevTools support
- Hot module replacement
- Server-side rendering (if applicable)
Implementation Pattern
// stores/[storeName].ts
import { defineStore } from 'pinia'
import type { [Type] } from '@/types'
export const use[StoreName]Store = defineStore('[storeName]', {
// State
state: () => ({
[property1]: [initialValue] as [Type],
[property2]: [] as [Type][],
[isLoading]: false,
[error]: null as string | null
}),
// Getters (computed values)
getters: {
[computedName]: (state) => {
return /* derive from state */
},
[filteredItems]: (state) => {
return state.[items].filter(/* condition */)
},
// Getter with argument
[getItemById]: (state) => {
return (id: string) => state.[items].find(item => item.id === id)
}
},
// Actions (methods)
actions: {
async fetch[Data]() {
this.[isLoading] = true
this.[error] = null
try {
const data = await api.get[Data]()
this.[property] = data
} catch (error) {
this.[error] = error.message
} finally {
this.[isLoading] = false
}
},
add[Item](item: [Type]) {
this.[items].push(item)
},
update[Item](id: string, updates: Partial<[Type]>) {
const index = this.[items].findIndex(item => item.id === id)
if (index !== -1) {
this.[items][index] = { ...this.[items][index], ...updates }
}
},
remove[Item](id: string) {
const index = this.[items].findIndex(item => item.id === id)
if (index !== -1) {
this.[items].splice(index, 1)
}
},
reset() {
this.$reset() // Reset to initial state
}
}
})
// Alternative: Composition API style (Setup Stores)
import { ref, computed } from 'vue'
export const use[StoreName]Store = defineStore('[storeName]', () => {
// State
const [property] = ref<[Type]>([initialValue])
const [items] = ref<[Type][]>([])
// Getters
const [computed] = computed(() => {
return /* derive from state */
})
// Actions
async function fetch[Data]() {
const data = await api.get[Data]()
[property].value = data
}
function add[Item](item: [Type]) {
[items].value.push(item)
}
return {
// Expose state
[property],
[items],
// Expose getters
[computed],
// Expose actions
fetch[Data],
add[Item]
}
})
// main.ts - Plugin setup
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
// Component usage (Composition API)
<script setup lang=\"ts\">
import { use[StoreName]Store } from '@/stores/[storeName]'
import { storeToRefs } from 'pinia'
const [storeName]Store = use[StoreName]Store()
// Get reactive references
const { [property], [computed] } = storeToRefs([storeName]Store)
// Access actions directly
[storeName]Store.fetch[Data]()
// Or destructure actions
const { add[Item], remove[Item] } = [storeName]Store
</script>
<template>
<div>
<p>{{ [property] }}</p>
<button @click=\"add[Item]([item])\">Add</button>
</div>
</template>
// Component usage (Options API)
<script lang=\"ts\">
import { mapState, mapActions } from 'pinia'
import { use[StoreName]Store } from '@/stores/[storeName]'
export default {
computed: {
...mapState(use[StoreName]Store, ['[property]', '[computed]'])
},
methods: {
...mapActions(use[StoreName]Store, ['fetch[Data]', 'add[Item]'])
}
}
</script>
State Persistence
import { defineStore } from 'pinia'
export const use[StoreName]Store = defineStore('[storeName]', {
state: () => ({
[property]: localStorage.getItem('[key]') || [default]
}),
actions: {
update[Property](value: [Type]) {
this.[property] = value
localStorage.setItem('[key]', value)
}
}
})
Best Practices
- Use Options API style for simple stores
- Use Composition API style for complex logic
- Always use TypeScript for type safety
- Use storeToRefs() for reactive destructuring
- Keep stores focused and single-purpose
- Use getters for computed values
- Actions can call other store actions
- Use $reset() to reset state
- Leverage Pinia DevTools for debugging
Tags
vue
pinia
state-management
typescript
Tested Models
gpt-4
claude-3-5-sonnet
\n
\n\n\n// Component usage (Options API)\n\n```\n\n## State Persistence\n\n```typescript\nimport { defineStore } from 'pinia'\n\nexport const use[StoreName]Store = defineStore('[storeName]', {\n state: () => ({\n [property]: localStorage.getItem('[key]') || [default]\n }),\n actions: {\n update[Property](value: [Type]) {\n this.[property] = value\n localStorage.setItem('[key]', value)\n }\n }\n})\n```\n\n## Best Practices\n- Use Options API style for simple stores\n- Use Composition API style for complex logic\n- Always use TypeScript for type safety\n- Use storeToRefs() for reactive destructuring\n- Keep stores focused and single-purpose\n- Use getters for computed values\n- Actions can call other store actions\n- Use $reset() to reset state\n- Leverage Pinia DevTools for debugging"}{{ [property] }}
\n \n