# Pinia
# What is Pinia?
A new state management library for the Vue ecosystem - officially supported
https://pinia.vuejs.org/ (opens new window)
# Benefits of Pinia
# Simpler API
Vuex: complexity and repetitiveness behind that action / mutation pattern. Every state change requires a lot of boilerplate.
Vuex:
- actions will commit a mutation in order to increment the count
- and the mutation itself, which actually increments
import { createStore } from 'vuex'
const CounterStore = createStore({
state() {
return {
count: 0
}
},
actions: {
increment(context) {
context.commit('incrementCount')
}
},
mutations: {
incrementCount(state) {
state.count++
}
}
})
Pinia: no mutations at all - directly inside of the action.
// The following is pseudocode
import { createStore } from 'pinia'
const CounterStore = createStore({
state() {
return {
count: 0
}
},
actions: {
increment() {
state.count++
}
}
})
Inside of the component, it can be called like a normal JavaScript function
// Calling an action with Vuex
store.$dispatch('increment')
// Calling an action with Pinia
store.increment()
# DevTools Support
Pinia also comes with its own set of dev tools.
# TypeScript Support
Vuex is not as TypeScript friendly. Pinia has first-class TypeScript support.
# Install
yarn add pinia
# or with npm
npm install pinia
register in main.js - add .use(createPinia())
import { createPinia } from 'pinia'
createApp(App)
.use(createPinia())
...
Add to Quasar (opens new window)
quasar new store <store_name>
quasar cli might need update:
npm i -d @quasar/cli@latest # or: yarn global add @quasar/cli quasar upgrade -i
# Defining a Store`
defineStore('<StoreName>', { <config> })
Pinia is modular by default
convention:
stores-folderdefineStore - good practice to name the ID the same as the filename
prefix export name woth
useeg
/stores/ProductStore.js:
import { defineStore} from "pinia";
export const useProductStore = defineStore('ProductStore', {
// state
// actions
// getters
})
# state()
function that returns an object
import { defineStore } from "pinia";
export const useUserStore = defineStore('UserStore', {
state() {
return {
user: 'John Doe'
}
}
})
alternative syntax:
export const useUserStore = defineStore('UserStore', { state: () => ({ user: 'John Doe' }) })
That’s all: store is ready to go
# Access state
https://pinia.vuejs.org/core-concepts/state.html (opens new window)
in the Component:
<script setup>
import {useProductStore} from "./stores/ProductStore";
const productStore = useProductStore() // invoke Function!!
</script>
or destructure (use
storeToRefs())import {storeToRefs} from 'pinia' const {products} = storeToRefs(useProductStore())
Or inside Options-API-Syntax:
<script>
import {useUserStore} from "./stores/UserStore";
export default {
...
setup() {
const userStore = useUserStore();
return {
userStore
}
}
...
}
</script>
# Read/Write
you can directly read and write to the state by accessing it through the store instance:
const store = useStore()
store.counter++
# Resetting the state (opens new window)
You can reset the state to its initial value by calling the $reset() method on the store:
const store = useStore()
store.$reset()
# Getters
synonymous to a computed prop in a component -> return a computed value based on the state
in vuex: you need to pass in the
stateicreateStore({ state: { events: [] }, getters: { numberOfEvents(state) { return state.events.length } } })use in template
<h1>{{$store.getters.numberOfEvents}}</h1>
# Standard Function Definition
access the state by using this
defineStore('UserStore', {
state: () => ({
user: 'John Doe'
}),
getters: {
firstName() {
return this.user.split(' ')[0]
}
}
})
# Arrow Function Definition
the
thiscontext is reset in every arrow function. -> passstateas the first argumentdefineStore('EventStore', { state: () => ({ events: [] }), getters: { numberOfEvents: state => state.events.length } })
# Using Getters from Pinia
<p>Logged in as {{userStore.firstName}}</p>
# Changing State - Actions
in vuex: actions and mutations.
📄 stores/index.js import { createStore } from 'vuex' export default createStore({ state: { events: [] }, mutations: { SET_EVENTS(state, events) { state.events = events } }, actions: { fetchEvents({ commit }) { return EventService.getEvents() .then(response => { commit('SET_EVENTS', response.data) }) .catch(error => { throw error }) } } })
in Pinia: no Mutations!
this.property = data
actions: {
fetchEvents() {
return EventService.getEvents()
.then(response => {
this.events = response.data
})
.catch(error => {
throw error
})
},
}
# Reset the State
reset to original values: $reset()
@click="cartStore.$reset()"
# Access Stores from other stores
import it and use it (but inside the actions?)
import {useAuthUserStore} from "./AuthUserStore";
actions: {
checkout() {
const authUserStore = useAuthUserStore()
alert(`${authUserStore.username} just bought ${this.count} items`)
},
# Hot Module Replacement
https://pinia.vuejs.org/cookbook/hot-module-replacement.html (opens new window)
import acceptHMRUpdate from pinia und am Ende des Stores hinzufügen:
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(<StoreName>, import.meta.hot))
}
# Persistent State
VueUse - Collection of composables
https://vueuse.org/ (opens new window)
install:
npm i @vueuse/core
In Store:
import {useLocalStorage} from '@vueuse/core';
...
state() {
return {
counter: useLocalStorage("counter", 1),
};
},
# Subscribing to Actions
to trigger something every time an action gets called
call $onAction() on the store in App.vue
cartStore.$onAction(({
name,
store,
args,
after,
onError
}) => {
if(name==='addItems') {
after(()=> {
console.log(args[0])
})
onError((error)=> {
console.log('tstsst', error)
})
}
})
# Subscribe to the State
$subscribe
perform sideeffects, whenever state changes
takes a callback, that receives 2 arguments
$state accesses the entire store
example: undo/redo
const history = reactive([])
const future = reactive([])
const doingHistory = ref(false)
history.push(JSON.stringify(cartStore.$state))
const redo = () => {
const latestState = future.pop()
if(!latestState) return
doingHistory.value = true
history.push(latestState)
cartStore.$state = JSON.parse(latestState)
doingHistory.value = false
}
const undo = () => {
if (history.length === 1) return
doingHistory.value = true
future.push(history.pop())
cartStore.$state = JSON.parse(history.at(-1))
doingHistory.value = false
}
cartStore.$subscribe((mutation, state) => {
if (!doingHistory.value) {
history.push(JSON.stringify(state))
future.splice(0, future.length) // empty the reactive array
}
})