@hootlex

@hootlex

Amsterdam

Alex Kyriakidis

Founder of Vue School

Author of The Majesty of Vue.js

Vue.js Contributor

Enterprise Consultant

#1 Training Platform for Vue.js

750+ Video Lessons

140,000 users

Alex Kyriakidis

Daniel Kelly

Debbie O'Brien

Maria Lamardo

Roman Kuba

Filip Rakowski

Lydia Hallie

Rolf Haug

Workshops

Vue Corporate Training

NEW

πŸ“Ί

Vue Video

Courses

πŸ‘¨β€πŸ«

Live Workshops

πŸ’¬

Bi-weekly Support

Sessions

πŸ§‘β€πŸ”§

Database for hiring

Vue devs

State Management in Vue.js with Pinia

Workshop Structure

  1. What is Pinia?
  2. Pinia State
  3. Pinia Actions
  4. Pinia Getters
  5. Using Stores in Other Stores
  6. Subscribe to Pinia Stores
  7. Pinia Plugins
  8. Advanced Concepts

Workshop Structure

Workshop Structure

πŸ‘¨β€πŸ« Instruction

πŸ’¬ Questions

πŸ‘©β€πŸ’» Hands-on Exercises

(10 - 20 mins)

(0 - 10 mins)

(15 - 30 mins)

πŸ“Ί Solution

(5 - 10 mins)

Workshop Goal

Master Pinia

πŸŽ‰ Have fun

Buddy System

πŸ§‘β€πŸ€β€πŸ§‘πŸ‘­πŸ‘¬

βœ… πŸ›‘ πŸ‘¨β€πŸ«πŸ‘©β€πŸ«

Meet Your Team

πŸ§‘β€πŸ€β€πŸ§‘πŸ‘­πŸ‘¬

πŸ’¬

πŸ’¬

πŸ’¬

@danielkelly_io

@danielkellyio

Alabama, USA

Daniel Kelly

Teacher @ Vue School

Full Stack developer (10 years)

Husband and Father

What is Pinia?

1

What is Pinia?

Global state management solution

  • Created by Eduardo San Martin Morote

What is Pinia?

Global state management solution

  • Created by Eduardo San Martin Morote
  • An experiment to flesh out the next version of Vuex

What is Pinia?

Global state management solution

  • Created by Eduardo San Martin Morote
  • An experiment to flesh out the next version of Vuex
  • Became the recommended global state management solution in the latest official Vue.js docs

What is Pinia?

Global state management solution

What is Pinia?

Global state management solution

What is Pinia?

Global state management solution

https://vuejs.org/guide/scaling-up/state-management.html#pinia

What is Global State Management?

What is Global State Management?

Local State Management

<script setup>
import {ref} from "vue"
const count = ref(0)  
</script>
<script>
export default {
  data(){
    return {
      count: 0
    }
  }
}
</script>

Composition API

Options API

Props

Events

What is Global State Management?

What is Global State Management?

What is Global State Management?

What is Global State Management?

NOT a replacement for props, events, and local data

Local vs Global State

How do I know which is which?

Β 

  • Data needed throughout page
  • Data needed across multiple pages
  • Data is application specific

Β 

  • Specific to component instance
  • Component is reusable

Global State

Local Data

Local vs Global State

How do I know which is which?

Examples of local data

<!-- LoginForm.vue -->
<script setup>
import { reactive } from "vue";  
const form = reactive({
  username: "",
  password: ""
})
</script>
<template>
<form @submit="$emit('login', form)">
  <label>
    Username
    <input v-model="form.username">
  </label>

  <label>
    Password
    <input v-model="form.password">
  </label>
</form>
</template>
  • Form data

Local vs Global State

How do I know which is which?

Examples of local data

<!--TwitterFeed.vue-->

<script setup>
import { ref } from "vue";  
const loading = ref(false);
  
function loadTweets(){
  loading.value = true;
  // load the data...
  loading.value = false;
}
//...
</script>
<template>
  <!-- tweets...-->
  <AppSpinner v-if="loading"/>
</template>
  • Form data
  • Loading State

Local vs Global State

How do I know which is which?

Examples of global state

<!-- AppHeader.vue -->

<template>
  <!--...-->
	<a class="/me">
      {{ user.name }}
  	</a>
</template>
  • Authenticated user
<!-- MyPosts.vue -->
<script>
  const fetchPosts = ()=>{
    fetch(`https://myendpoint.com/user-posts/${user.id}`)
  }
//...
</script>
<!-- Profile.vue -->
<!-- ProfileEditor.vue -->
<!-- AppFooter.vue -->
<!-- etc -->

Local vs Global State

How do I know which is which?

Examples of global state

<!-- PostHero.vue -->
<template>
  <!--...-->
	<div class="hero">
      {{ post.title }}
  	</div>
</template>
  • Authenticated user
  • Blog post
<!-- PostShow.vue -->
<script>
  const fetchPost = ()=>{
    fetch(`https://myendpoint.com/posts/${post.id}`)
  }
//...
</script>
<!-- PostByLine.vue -->
<!-- PostComments.vue -->
<!-- PostBody.vue -->
<!-- etc -->

What about Vuex?

What about Vuex?

Pinia provides several advantages:

  • Pinia is modular by default (great for organization and code splitting)
  • Pinia removes mutations
  • Pinia is more intuitive
  • Pinia has full type support for TypeScript
  • We'll see examples of all of these throughout the workshop

What about Vuex?

https://vueschool.io/articles/vuejs-tutorials/how-to-migrate-from-vuex-to-pinia/

Can I use it with Vue 2?

Can I use it with Vue 2?

Yes!

  • Must install the @vue/composition-api plugin
  • There are a few small caveats
  • Can use with composition API or Options API
  • During this course, we'll focus on Composition API

How to Install Pinia

How to Install Pinia

npm init vue@3

for a new project

How to Install Pinia

npm install pinia
// main.js
import { createApp } from "vue";
import App from "./App.vue";
import { createPinia } from "pinia";


createApp(App)
  .use(createPinia())
  .mount("#app");

in an existing project

Create Your First Store

Create Your First Store

Pinia is modular by default

Create Your First Store

Pinia is modular by default

Create Your First Store

Pinia is modular by default

Store

Posts

Store

Users

Users

Store

Comments

Blog Application

Create Your First Store

// stores/PostStore.js

import { defineStore } from 'pinia' 
  • a store is a regular .js or .ts file
  • lives in a "stores" directory by convention
  • use defineStore from Pinia define file as a store

Create Your First Store

// stores/PostStore.js

import { defineStore } from 'pinia' 

defineStore('PostStore')
  • first argument is store name
  • name is required
  • used to connect store to Devtools
  • My preference to name it the same as the filename

Create Your First Store

// stores/PostStore.js

import { defineStore } from 'pinia' 

defineStore('PostStore', {
  // state
  // actions
  // getters
})

second argument is store options object

Create Your First Store

// stores/PostStore.js

import { defineStore } from 'pinia' 

defineStore('PostStore', () => {
  return { 
    // data
    // functions
    // etc
  }
})

second argument can also be a function similar to component setup()

Create Your First Store

// stores/PostStore.js

import { defineStore } from 'pinia' 

export const usePostStore = defineStore('PostStore', {
  // state
  // actions
  // getters
})
  • expose the store by exporting it
  • convention to prefix with `use`

Create Your First Store

// stores/PostStore.js

import { defineStore } from 'pinia' 

export const usePostStore = defineStore('PostStore', {
  // state
  // actions
  // getters
})



if (import.meta.hot) {
  import.meta.hot.accept(acceptHMRUpdate(usePostStore, import.meta.hot));
}

support HMR but must provide a snippet

Create Your First Store

// App.vue
<script setup>
import { usePostStore } from "@/stores/PostStore"
const postStore = usePostStore();
</script>

use the store by importing the use function and calling it

Create Your First Store

Questions?

What We're Buliding

What We're Buliding

What We're Buliding

Let's take a look at:

  • The boilerplate code
  • and the existing app in the browser

πŸ‘©β€πŸ’»πŸ‘¨πŸ½β€πŸ’»Assignment #1 (15 mins)

  • Download the boilerplate code
  • Install pinia via npm
  • Register the Pinia plugin
  • Create a product store
  • Create a cart store
  • Use both stores in CartWidget.vue and the product store in App.vue

Goal: Practice installing Pinia, defining stores, and using stores in components

Pinia State

2

Define Pinia State

// stores/ProductStore.ts

import { defineStore } from "pinia";

export const useProductStore = defineStore("ProductStore", {
  state: () => {
    return {};
  },
});

Must be a function

  • Allows Pinia to work on both client side and server side
  • the arrow function is recommended for full type inference

Define Pinia State

// stores/ProductStore.ts

import { defineStore } from "pinia";

export const useProductStore = defineStore("ProductStore", {
  state: () => {
    return {
       	name: "The Pineapple Stand",
      	products: [
          "Plain Ol' Pineapple",
          "Dried Pineapple",
          "Pineapple Gum",
          "Pineapple T-Shirt",
      	],
      	isAwesome: true,
    };
  },
});

Access Pinia State

// stores/App.vue

<script setup>
import { useProductStore } from "./stores/ProductStore";
const productStore = useProductStore();
console.log(productStore.name) // "The Pineapple Stand"
</script>

...

State available as property on store

Access Pinia State

State is TypeSafe

Access Pinia State

// stores/App.vue

<script setup>
import { useProductStore } from "./stores/ProductStore";
const { name } = useProductStore();
console.log(name) // "The Pineapple Stand"
</script>

...

⚠️ Name is no longer reactive

Can de-structure state from store

Access Pinia State

// stores/App.vue

<script setup>
import { useProductStore } from "./stores/ProductStore";
import { storeToRefs } from "pinia";
  
const { name } = storeToRefs(useProductStore());
console.log(name.value) // "The Pineapple Stand"
</script>

...

When de-structuring convert store to refs to maintain reactivity

Update Pinia State

// stores/App.vue

<script setup>
import { useProductStore } from "./stores/ProductStore";
import { storeToRefs } from "pinia";
const { name } = storeToRefs(useProductStore());
name.value = "Hello new name";
</script>

State can be directly updated

Update Pinia State

// stores/App.vue

<script setup>
import { useProductStore } from "./stores/ProductStore";
import { storeToRefs } from "pinia";
const productStore = useProductStore();
productStore.name = "Hello new name";
</script>

No need for .value if you don't de-destructure

Update Pinia State

// stores/App.vue

<script setup>
import { useProductStore } from "./stores/ProductStore";
import { storeToRefs } from "pinia";
const { name } = storeToRefs(useProductStore());
</script>

<template>
<input v-model="name" type="text" />
</template>

Use with v-model is easy!

Update Pinia State

Update Pinia State

// stores/App.vue

<script setup>
import { useProductStore } from "./stores/ProductStore";
import { storeToRefs } from "pinia";
const { name: newName } = storeToRefs(useProductStore());
</script>

Can rename de-structured state

Pinia State

Useful feature you don't get if you roll your own stores with vanilla composition API.

Pinia State

Comes with some built in helpers

store.$reset()
// acccess the entire state or 
// set it all at once
store.$state

Questions?

πŸ‘©β€πŸ’»πŸ‘¨πŸ½β€πŸ’»Assignment #2 (20 mins)

  • Define state products on the ProductStore from products.json
  • Access products state from the store in App.vue to display product cards
  • Define state items on the cart store from cart.json
  • Access items state from the cart store in CartWidget.vue and display a CartItem component for each item in the state
  • Bonus: Try accessing the products state using both with and without destructuring

https://tinyurl.com/38jpcarr

Goal: Practice defining and accessing state in a Pinia store

https://tinyurl.com/6h5d54uf

Live Slides

https://tinyurl.com/mtfzertx

Slides

Pinia Actions

3

Pinia Actions

What are they?

  • Methods for mutating the store's state
  • can think of them like component methods but for the store
  • provide standardized and named mechanisms for mutating the state
  • are great for feedback in the devtools
  • consolidate business logic
  • can be asynchronous or synchronous

Pinia Actions

import { defineStore } from "pinia";
import products from "@/data/products.json";
export const useProductStore = defineStore("ProductStore", {
  state: //...
  actions:{
    fill(){}
  }
});

Define actions as methods on the actions option

Pinia Actions

import { defineStore } from "pinia";
export const useProductStore = defineStore("ProductStore", {
  state: ()=>{
    return {
      products:[],
    }
  },
  actions:{
    async fill(){
      const res = await fetch('my_api_endpoint/products');
      this.products = await res.json();
    }
  }
});

access state with `this`

Great for filling state with initial data

Pinia Actions

State is fully typed on `this` within actions

Pinia Actions

Call actions in components as store methods

<script setup>
const productStore = useProductStore();
productStore.fill();
</script>

Pinia Actions

Or de-structure it from the store

<script setup>
const { fill } = useProductStore();
fill();
</script>

Pinia Actions

You CANNOT get actions when using storeToRefs

<script setup>
const { fill } = storeToRefs(useProductStore());
fill(); // Error
</script>

❌

won't work

Pinia Actions

import { defineStore } from "pinia";

export const useCartStore = defineStore("CartStore", {
  state: () => {
    return {
      items: [],
    };
  },
  actions: {
    addItem(itemId, count) {
      // set the count for the proper item in the state above
    },
  },
});

Useful for updating state based on user interaction

// App.vue
<button @click="addItem(product.id, $event)">Add Product to Cart</button>

Pinia Actions

Mutations grouped in the devtools

Pinia $patch

Alternate way of grouping mutations to state

productStore.$patch({
  name: "Vue Conf US",
  location: "Ft Lauderdale, FL",
});

Pinia $patch

Alternate way of grouping mutations to state

Pinia $patch

Can also use a callback function

cartStore.$patch((state) => {
  state.items.push({ name: 'shoes', quantity: 1 })
  state.hasChanged = true
})

Questions?

πŸ‘©β€πŸ’»πŸ‘¨πŸ½β€πŸ’»Assignment #3 (20 mins)

  • Create an async action to fill the product store with it’s initial data.

Β 

https://tinyurl.com/5xa26z9p

Goal: Practice defining and calling actions in a Pinia store

  • Create an action to add items to the cart whenever the "add to cart button" on a product card is pushed.

Β 

Pinia Getters

4

Pinia Getters

What are they?

equivalent of computed props on a component

Pinia Getters

// ProductStore.ts
import { defineStore } from "pinia";
import type { Product } from "@/types";
export const useProductStore = defineStore("ProductStore", {
  state: () => {
    return {
      products: [] as Product[],
    };
  },
  getters: {
    count() {
      return this.products.length;
    },
  },
  actions: {
    //...
  },
});

access state with `this`

Pinia Getters

// ProductStore.ts
import { defineStore } from "pinia";
import type { Product } from "@/types";
export const useProductStore = defineStore("ProductStore", {
  state: () => {
    return {
      products: [] as Product[],
    };
  },
  getters: {
    count(): number {
      return this.products.length;
    },
  },
  actions: {
    //...
  },
});

access state with `this`

Pinia Getters

// ProductStore.ts
import { defineStore } from "pinia";
import type { Product } from "@/types";
export const useProductStore = defineStore("ProductStore", {
  state: () => {
    return {
      products: [] as Product[],
    };
  },
  getters: {
    count: (state) => {
      return state.products.length;
    },
  },
  actions: {
    //...
  },
});

access state with `state`

Pinia Getters

// ProductStore.ts
import { defineStore } from "pinia";
import type { Product } from "@/types";
export const useProductStore = defineStore("ProductStore", {
  state: () => {
    return {
      products: [] as Product[],
    };
  },
  getters: {
    count: (state) => state.products.length,
  },
  actions: {
    //...
  },
});

encourages single

line arrow functions

Pinia Getters

// ProductStore.ts
import { defineStore } from "pinia";
import type { Product } from "@/types";
export const useProductStore = defineStore("ProductStore", {
  state: () => {
    return {
      products: [] as Product[],
    };
  },
  getters: {
    count: (state) => state.products.length,
    doubleCount(): number {
      return this.count * 2;
    },
  },
  actions: {
    //...
  },
});

access other

getters on `this`

Pinia Getters

// ProductStore.ts
import { defineStore } from "pinia";
import type { Product } from "@/types";
export const useProductStore = defineStore("ProductStore", {
  state: () => {
    return {
      products: [] as Product[],
    };
  },
  getters: {
    count: (state) => state.products.length,
    doubleCount: (state) => state.count * 2,
  },
  actions: {
    //...
  },
});

access other

getters on `this`

Pinia Getters

Pinia Getters

// ProductStore.ts
import { defineStore } from "pinia";
import type { Product } from "@/types";
export const useProductStore = defineStore("ProductStore", {
  state: () => {
    return {
      products: [] as Product[],
    };
  },
  getters: {
    count: (state) => state.products.length,
    doubleCount: (state) => state.count * 2,
    productByName: (state) => (name) => {
      return state.products.find((product) => product.name === name);
    },
  },
});

accept arguments

by returning a function

Dynamic Getters

// ProductStore.ts
import { defineStore } from "pinia";
import type { Product } from "@/types";
export const useProductStore = defineStore("ProductStore", {
  state: () => {
    return {
      products: [] as Product[],
    };
  },
  getters: {
    count: (state) => state.products.length,
    doubleCount: (state) => state.count * 2,
    productByName(state) {
      return function (name) {
        return state.products.find((product) => product.name === name);
      };
    },
  },
});

accept arguments

by returning a function

Pinia Getters

Dynamic Getters

Pinia Getters

// App.vue
<script setup>
const productStore = useProductStore();
console.log(productStore.count) // 4
</script>

Access getters as a property on the store

Pinia Getters

// App.vue
<script setup>
import { storeToRefs } from "pinia";
const { count } = storeToRefs(useProductStore());
console.log(count.value) // 4
</script>

Can de-structure getters from store but must use `storeToRefs`

Questions?

πŸ‘©β€πŸ’»πŸ‘¨πŸ½β€πŸ’»Assignment #4 (20 mins)

  • Display the accurate number of items in the cart with a getter from the cart store

https://tinyurl.com/2x6k5u9d

Goal: Practice defining and accessing Pinia getters

  • Use a getter in a getter (hint: could call it `isEmpty`) then use that getter to conditionally show a cart is empty message.

Using Stores in Other Stores

5

Using Stores in Other Stores

// CartStore.ts
import { defineStore } from "pinia";
import { useProductStore } from "./ProductStore";

export const useCartStore = defineStore("CartStore", {
  // ...
  getters: {
    allProducts(){
      const productStore = useProductStore();
      return productStore.products
    }
  },
  //...
});

Use state and getters from another store in a getter or an action

Using Stores in Other Stores

// CartStore.ts
import { defineStore } from "pinia";
import { useProductStore } from "./ProductStore";

export const useCartStore = defineStore("CartStore", {
  //...
  actions:{
    reloadProducts(){
      const productStore = useProductStore();
      return productStore.fill()
    }
  }
  //...
});

Use actions from another store in an action

Using Stores in Other Stores

// CartStore.ts
import { defineStore } from "pinia";
import { useProductStore } from "./ProductStore";

❌ const productStore = useProductStore();

export const useCartStore = defineStore("CartStore", {
  //...
  actions:{
    reloadProducts(){
      return productStore.fill()
    }
  }
	//...
});

Use actions from another store in an action

Using Stores in Other Stores

// CartStore.ts
import { defineStore } from "pinia";
import { useProductStore } from "./ProductStore";

❌ const productStore = useProductStore();

export const useCartStore = defineStore("CartStore", {
  //...
  actions:{
    reloadProducts(){
      return productStore.fill()
    }
  }
	//...
});

Use actions from another store in an action

Questions?

πŸ‘©β€πŸ’»πŸ‘¨πŸ½β€πŸ’»Assignment #5 (15 mins)

  • Display the accurate cart total where the hard-coded $40 exists now

https://tinyurl.com/5f33te88

Goal: Practice using stores in other stores

Subscribe to Pinia Stores

6

Subscribe to Pinia Stores

  • watch state for changes
  • watch actions for calls
  • and perform side affects

Subscribe to Pinia Stores

  • watch state for changes
  • watch actions for calls
  • and perform side affects
  • measuring how long your actions take to run
  • triggering user notifications
  • logging errors to 3rd party services

Subscribe to Pinia Stores

someStore.$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
  }) => {
    console.log(name, store, args, after, onError);
  }
);

Subscribe to actions

Subscribe to Pinia Stores

someStore.$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
  }) => {
    if(name === 'fill'){
      //... do the things here  
    }
  }
);

Use conditional to run on select actions

Subscribe to Pinia Stores

someStore.$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
  }) => {
    after( result =>{
      console.log(`the action is complete and returned ${result}`)
    })
    onError(error =>{
      console.log(`the action failed and the error thrown is ${error}`)
    })
  }
);

Example of after and onError

Subscribe to Pinia Stores

someStore.$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
  }) => {
    after( result =>{
      console.log(`the action is complete and returned ${result}`)
    })
    onError(error =>{
      console.log(`the action failed and the error thrown is ${error}`)
    })
  }
);

Example of after and onError

Subscribe to Pinia Stores

someStore.$subscribe((mutation, state) => {
  
  // 'direct' | 'patch object' | 'patch function'
  mutation.type 
  
  // same as cartStore.$id
  mutation.storeId
  
  // only available with mutation.type === 'patch object'
  mutation.payload // patch object passed to $patch()
})

Subscribing to the state

Subscribe to Pinia Stores

someStore.$subscribe((mutation, state) => {
  
  // 'direct' | 'patch object' | 'patch function'
  mutation.type 
  
  someStore.myState = "something else"
})

Subscribing to the state

Subscribe to Pinia Stores

someStore.$subscribe((mutation, state) => {
  
  // 'direct' | 'patch object' | 'patch function'
  mutation.type 
  
  someStore.$patch({
    myState: "something else",
    otherState: true
  })
})

Subscribing to the state

Subscribe to Pinia Stores

someStore.$subscribe((mutation, state) => {
  
  // 'direct' | 'patch object' | 'patch function'
  mutation.type 
  
  someStore.$patch((state)=>{
    state.myState = "something else"
  })
})

Subscribing to the state

Subscribe to Pinia Stores

someStore.$subscribe((mutation, state) => {
  
  // 'direct' | 'patch object' | 'patch function'
  mutation.type 
  
  // same as cartStore.$id
  mutation.storeId
  
  // only available with mutation.type === 'patch object'
  mutation.payload // patch object passed to $patch()
})

Subscribing to the state

Subscribe to Pinia Stores

someStore.$subscribe((mutation, state) => {
  
  // 'direct' | 'patch object' | 'patch function'
  mutation.type 
  
  // same as cartStore.$id
  mutation.storeId

  defineStore("someStore", {/*...*/})
})

Subscribing to the state

Subscribe to Pinia Stores

someStore.$subscribe((mutation, state) => {
  
  // 'direct' | 'patch object' | 'patch function'
  mutation.type 
  
  // same as cartStore.$id
  mutation.storeId
  
  // only available with mutation.type === 'patch object'
  mutation.payload // patch object passed to $patch()
})

Subscribing to the state

Subscribe to Pinia Stores

someStore.$subscribe((mutation, state) => {
  localStorage.setItem('myState', JSON.stringify(state))
})

Subscribing to the state

Questions?

πŸ‘©β€πŸ’»πŸ‘¨πŸ½β€πŸ’»Assignment #6 (25 mins)

  • Whenever the user adds a new item to the cart, display an alert showing how many items were added
  • Persist the state of the CartStore to localStorage and use them as the cartStore state on page load for a persistent cart.

https://tinyurl.com/3sx3p7sd

Goal: Practice subscribing to Pinia actions and state

Pinia Plugins

7

Pinia Plugins

Just like Vue has plugins to quickly add advanced functionality...

Pinia Plugins

...so does Pinia

Pinia Plugins

One advantages of the standardization provided by an official store solution over vanilla CAPI store

Pinia Plugins

What can they do?

  • Add new properties to stores
  • Add new options when defining stores
  • Add new methods to stores
  • Wrap existing methods
  • Change or even cancel actions
  • Implement side effects like Local Storage

Pinia Plugins

How to build

//PiniaLocalStoragePlugin.ts
export function PiniaLocalStoragePlugin({ 
  pinia, 
  app, 
  store, 
  options
}) {
  
}
// the pinia created with `createPinia()`
// the current app created with `createApp()` (Vue 3 only)
// the store the plugin is augmenting
// the options object defining the store passed to `defineStore()`

Pinia Plugins

How to build

//PiniaLocalStoragePlugin.ts
export function PiniaLocalStoragePlugin({ 
  pinia, 
  app, 
  store, 
  options
}) {
  const localStorageKey = `PiniaStore_${store.$id}`;
  
  store.$subscribe((mutation, state) => {
    localStorage.setItem(localStorageKey, JSON.stringify(state));
  });
  
  const savedState = localStorage.getItem(localStorageKey);
  if (savedState) {
    store.$state = JSON.parse(savedState);
  }
  
}

Pinia Plugins

Applies to all stores by default

//PiniaLocalStoragePlugin.ts
export function PiniaLocalStoragePlugin({ 
  pinia, 
  app, 
  store, 
  options
}) {
  const localStorageKey = `PiniaStore_${store.$id}`;
  
  store.$subscribe((mutation, state) => {
    localStorage.setItem(localStorageKey, JSON.stringify(state));
  });
  
  const savedState = localStorage.getItem(localStorageKey);
  if (savedState) {
    store.$state = JSON.parse(savedState);
  }
  
}

Pinia Plugins

Making it opt-in

//CartStore.ts
export const useCartStore = defineStore("CartStore", {
  localStorage: true,
  state: ()=>{ /*...*/ },
  getters: {/*...*/}
})

Pinia Plugins

Making it opt-in

//PiniaLocalStoragePlugin.ts
export function PiniaLocalStoragePlugin({ 
  pinia, 
  app, 
  store, 
  options
}) {
  if (!options.localStorage) return;
  
  const localStorageKey = `PiniaStore_${store.$id}`;
  
  store.$subscribe((mutation, state) => {
    localStorage.setItem(localStorageKey, JSON.stringify(state));
  });
  
  const savedState = localStorage.getItem(localStorageKey);
  if (savedState) {
    store.$state = JSON.parse(savedState);
  }
  
}

Pinia Plugins

Type the new option

import "pinia";

declare module "pinia" {
  export interface DefineStoreOptionsBase<S, Store> {
    // allow defining a boolean
    // for local storeage plugin
    localStorage?: boolean;
  }
}

Pinia Plugins

extend the DefineStoreOptionsBase

// main.ts
//...
import { PiniaLocalStoragePlugin } from "./plugins/...";

const pinia = createPinia()
	.use(PiniaLocalStoragePlugin);

createApp(App)
  .use(pinia)
  .mount("#app");

Pinia Plugins

Use the plugin

//PiniaAxiosPlugin.ts
import axios from "axios";

export function PiniaAxiosPlugin({ 
  pinia, 
  app, 
  store, 
  options
}) {
  store.axios = axios;
  
}

Pinia Plugins

Another example: adding axios

// ProductStore.ts

export const useProductStore = defineStore("ProductStore", {
  //...
  actions: {
    async fill() {
      const res = await this.axios.get("/products.json");
      this.products = res.data;
    },
  },
});

Pinia Plugins

Another example: adding axios

//PiniaAxiosPlugin.ts
import axios from "axios";
import { markRaw } from "vue";

export function PiniaAxiosPlugin({ 
  pinia, 
  app, 
  store, 
  options
}) {
  store.axios = markRaw(axios);
}

Pinia Plugins

Another example: adding axios

Pinia Plugins

Typing axios on stores

import "pinia";
import type { Axios } from "axios";

declare module "pinia" {
  export interface PiniaCustomProperties {
    axios: Axios;
  }
}

Pinia Plugins

Typing axios on stores

Pinia Plugins

3rd party pinia plugins

  • There is no awesome Pinia repo
  • Can search npm for Pinia plugins

Questions?

πŸ‘©β€πŸ’»πŸ‘¨πŸ½β€πŸ’»Assignment #7 (25 mins)

  • Create an undo/redo Pinia plugin that exposes undo and redo actions on a store
  • useRefHistory from VueUse could be very helpful in this
  • This is a tough one, feel free to ask for help!

https://tinyurl.com/38j9kswt

Goal: Practice creating and using a Pinia plugin

Defining a Store Using a Setup Function

8

Defining a Store Using a Setup Function

//CounterStore.ts
export const useCounterStore = defineStore('CounterStore', () => {
  const count = ref(0)
  function increment() {
    count.value++
  }

  return { count, increment }
})

Defining a Store Using a Setup Function

Why?

Defining a Store Using a Setup Function

Why?

  • style preference
  • can watch state from within the store

Defining a Store Using a Setup Function

//CounterStore.ts
export const useCounterStore = defineStore('CounterStore', () => {
  const count = ref(0)
  function increment() {
    count.value++
  }
  
  watch(count, ()=>{
    // save some data to the server or whatever
  })

  return { count, increment }
})

Defining a Store Using a Setup Function

//CounterStore.ts
import { watchDebounced } from '@vueuse/core'

export const useCounterStore = defineStore('CounterStore', () => {
  const count = ref(0)
  function increment() {
    count.value++
  }
  
  watchDebounced(count, ()=>{
    // save some data to the server or whatever
  }, { debounce: 500 })

  return { count, increment }
})

Questions?

πŸ‘©β€πŸ’»πŸ‘¨πŸ½β€πŸ’»Assignment #8 (25 mins)

  • Define a counter store that uses the setup function style
  • Expose a reactive count and increment function from the store
  • Watch the count and sync the count's value to server storage whenever it changes
  • A free and easy key-value storage you can use for this exercise is https://kvdb.io/

https://tinyurl.com/2p9atauj

Goal: Practice using the setup function style of defining a store

Q&A

Pinia: The Enjoyable Vue Store

Other Great Courses Too!

Vue Corporate Training

NEW

πŸ“Ί

Vue Video

Courses

πŸ‘¨β€πŸ«

Live Workshops

πŸ’¬

Bi-weekly Support

Sessions

πŸ§‘β€πŸ”§

Database for hiring

Vue devs

Thanks!

Copy of 1. Pinia Workshop

By Alex Kyriakidis

Copy of 1. Pinia Workshop

  • 685