Pinia Advanced Feature in Vue 3

Pinia Advanced Feature in Vue 3

2023-09-29
4 min read

Introduction

Pinia has pushed Vuex out in Vue 3 and risen as the most popular state management package. The article is going to introduce some advanced features in Pinia.

Setup Store

The Setup store allows to use store similar as <script setup />Setup store는 `option store` 와 달리 `

Compare with option store

typescript
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const name = ref('Eduardo')
  
  const doubleCount = computed(() => count.value * 2)
  
  const increament() => {
    count.value++
  }

  return { count, name, doubleCount, increment }
})

The Setup store is a great feature, but I am using option store because many examples are written by option store. Many references are also written by option store

storeToRefs

The storeToRefs allows to use Destructing assignment for state and getters in store and they can be updated .Vue file directrly.

typescript
const { count, doubleCount } = storeToRefs(useCounterStore())
count.value = 100

It's wrapped by ref, reactivity API, so it's easy to update value without mutation. For the Action, storeToRefs() is not necessary.

Subscription

The Subscription feature provide better performence than watch. The Subscription feature can watch both timing that state is updating or action is called

State

typescript
counterStore.$subscribe((mutation, state) => {
  // import { MutationType } from 'pinia'
  mutation.type // 'direct' | 'patch object' | 'patch function'
  // Store id
  mutation.storeId
  // mutation.type === 'patch object'일때만 사용 가능함
  mutation.payload // store.$patch()로 들어옴

  console.log('state is changed', state)
})

The $subscribe will fire when one of state is update. See more about state subscription

Action

typescript
const counterStore = uesCounter()
const unsubscribe = counterStore.$onAction(
  ({
     name, // name of the action
     store, // store instance, same as `someStore`
     args, // array of parameters passed to the action
     after, // hook after the action returns or resolves
     onError, // hook if the action throws or rejects
   }) => {
    // a shared variable for this specific action call
    const startTime = Date.now()
    // this will trigger before an action on `store` is executed
    console.log(`Start "${name}" with params [${args.join(', ')}].`)

    // this will trigger if the action succeeds and after it has fully run.
    // it waits for any returned promised
    after((result) => {
      console.log(
        `Finished "${name}" after ${
          Date.now() - startTime
        }ms.\nResult: ${result}.`
      )
    })

    // this will trigger if the action throws or returns a promise that rejects
    onError((error) => {
      console.warn(
        `Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
      )
    })
  }
)

// manually remove the listener
unsubscribe()

The example code is from official document The $onAction will fire when one of actions is called See more about state subscription

Middleware

Although there is exact middleware that official document explains, there is a way to make featre like middleware

typescript
pinia.use(({ store }) => {
  store.$subscribe(() => {
    // react to store changes
  })
  store.$onAction((store, name, args) => {
    // react to store actions
  })
})

The $subscribe and $onAction in pinia will watch when one of states or action is changed or called about all stores

Persist

There are 2 ways to save state permanently.

Localstorage

Simply, save data to localstorage and get from localstorage.

typescript
export const useCounterStore = defineStore('counter', {
  state: () => ({ 
    count: localStorage.getItem('pinia/counter/count'), 
    name: useLocalStorage('pinia/counter/name', 'bob').
  }),
  getters: {
    doubleCount: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++
      localStorage.setItem('counter/count', this.count)
    },
  },
})

To delete saved data, go to F12 -> Application -> Local Storage. In addition, it's allowed to use useLocalStroage() in VueUse package. Look at the name in state

Use with Subscribtion

typescript
counterStore.$subscribe((mutation, state) => {
  localStorage.setItem('pinia/counter/count', state.counter)
})

Auto save feature can be developed by using The $subscribe explained above

Package

Introducing Package also use same way, but it's much easier. Click the Official document to see more details

First of all, install package

bash
npm i pinia-plugin-persistedstate
yarn add pinia-plugin-persistedstate
pnpm i pinia-plugin-persistedstate

Register package plugin to pinia

typescript
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

Set persist in store.

typescript
export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0, name: 'Eduardo' }),
  getters: {
    doubleCount: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++
    },
  },
  persist: true
})

Simply add persist: true to store. States will be saved localstorage automatically.

Other storages are also supported.

typescript
  persist: {
    storage: persistedState.sessionStorage,
  },

Devtool

You can debug all store with Chrome Vue devtool. Chrome extension Just install the extension and click the pinia tab.

References

Free open source project made by Youngjin