defineStore
defineStore()
is the main function for creating stores in Pinia.
Syntax
js
defineStore(id, options)
defineStore(id, setupFunction)
defineStore(options)
Parameters
id
(string)
A unique identifier for the store. This is used for:
- DevTools integration
- Server-side rendering
- Store registration
js
defineStore('counter', { /* options */ })
options
(object)
Store configuration object with the following properties:
state
(function)
A function that returns the initial state of the store.
js
defineStore('counter', {
state: () => ({
count: 0,
name: 'Eduardo'
})
})
getters
(object)
Computed properties for the store. Each getter receives the state as the first parameter.
js
defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
doubleCount: (state) => state.count * 2,
// Accessing other getters
quadrupleCount() {
return this.doubleCount * 2
},
// With TypeScript
tripleCount: (state): number => state.count * 3
}
})
actions
(object)
Methods that can modify the state. They have access to the whole store instance via this
.
js
defineStore('counter', {
state: () => ({ count: 0 }),
actions: {
increment() {
this.count++
},
async fetchData() {
const data = await api.getData()
this.count = data.count
},
incrementBy(amount) {
this.count += amount
}
}
})
setupFunction
(function)
A function that defines the store using the Composition API style.
js
import { ref, computed } from 'vue'
defineStore('counter', () => {
// State
const count = ref(0)
const name = ref('Eduardo')
// Getters
const doubleCount = computed(() => count.value * 2)
// Actions
function increment() {
count.value++
}
async function fetchData() {
const data = await api.getData()
count.value = data.count
}
return {
count,
name,
doubleCount,
increment,
fetchData
}
})
Return Value
Returns a function that can be called to get the store instance.
js
const useCounterStore = defineStore('counter', { /* options */ })
// In a component
const store = useCounterStore()
Examples
Basic Store
js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment() {
this.count++
}
}
})
Composition API Style
js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
With TypeScript
ts
import { defineStore } from 'pinia'
interface State {
count: number
name: string
}
interface Getters {
doubleCount: (state: State) => number
}
interface Actions {
increment(): void
setName(name: string): void
}
export const useCounterStore = defineStore<'counter', State, Getters, Actions>('counter', {
state: (): State => ({
count: 0,
name: 'Eduardo'
}),
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment() {
this.count++
},
setName(name: string) {
this.name = name
}
}
})
Advanced Example
js
import { defineStore } from 'pinia'
import { api } from '@/services/api'
export const useUserStore = defineStore('user', {
state: () => ({
user: null,
loading: false,
error: null
}),
getters: {
isLoggedIn: (state) => !!state.user,
userName: (state) => state.user?.name || 'Guest',
hasError: (state) => !!state.error
},
actions: {
async login(credentials) {
this.loading = true
this.error = null
try {
const user = await api.login(credentials)
this.user = user
} catch (error) {
this.error = error.message
} finally {
this.loading = false
}
},
logout() {
this.user = null
this.error = null
},
clearError() {
this.error = null
}
}
})
Best Practices
1. Use Descriptive IDs
js
// ✅ Good
defineStore('userProfile', { /* ... */ })
defineStore('shoppingCart', { /* ... */ })
// ❌ Avoid
defineStore('store1', { /* ... */ })
defineStore('data', { /* ... */ })
2. Keep State Flat
js
// ✅ Good
state: () => ({
firstName: '',
lastName: '',
email: ''
})
// ❌ Avoid deep nesting
state: () => ({
user: {
profile: {
personal: {
firstName: '',
lastName: ''
}
}
}
})
3. Use Actions for State Mutations
js
// ✅ Good
actions: {
updateUser(userData) {
this.firstName = userData.firstName
this.lastName = userData.lastName
}
}
// ❌ Avoid direct state mutation in components
// store.firstName = 'John' // Don't do this
4. Handle Async Operations Properly
js
actions: {
async fetchUser(id) {
this.loading = true
this.error = null
try {
const user = await api.getUser(id)
this.user = user
} catch (error) {
this.error = error.message
} finally {
this.loading = false
}
}
}
Common Patterns
Loading States
js
defineStore('data', {
state: () => ({
items: [],
loading: false,
error: null
}),
actions: {
async fetchItems() {
this.loading = true
this.error = null
try {
this.items = await api.getItems()
} catch (error) {
this.error = error.message
} finally {
this.loading = false
}
}
}
})
Computed Properties
js
defineStore('todos', {
state: () => ({
todos: []
}),
getters: {
completedTodos: (state) => state.todos.filter(todo => todo.completed),
pendingTodos: (state) => state.todos.filter(todo => !todo.completed),
todoCount: (state) => state.todos.length,
completionRate() {
return this.todoCount === 0 ? 0 : this.completedTodos.length / this.todoCount
}
}
})
Form Handling
js
defineStore('form', {
state: () => ({
formData: {
name: '',
email: '',
message: ''
},
errors: {},
submitting: false
}),
actions: {
updateField(field, value) {
this.formData[field] = value
// Clear error when user starts typing
if (this.errors[field]) {
delete this.errors[field]
}
},
async submitForm() {
this.submitting = true
this.errors = {}
try {
await api.submitForm(this.formData)
this.resetForm()
} catch (error) {
this.errors = error.validationErrors || {}
} finally {
this.submitting = false
}
},
resetForm() {
this.formData = { name: '', email: '', message: '' }
this.errors = {}
}
}
})