Skip to content

Instantly share code, notes, and snippets.

@iyusa
Last active December 10, 2023 11:00
Show Gist options
  • Save iyusa/51af7277f3055b2ac0de1997a90f14d1 to your computer and use it in GitHub Desktop.
Save iyusa/51af7277f3055b2ac0de1997a90f14d1 to your computer and use it in GitHub Desktop.
[Pinia]#pinia #store #vue #nuxt

What is Pinia

Store library for vue. To store state across components or pages.

Note

You can use this code to share global state n simple app

export const state = reactive({})

but not good for SSR.

What is a Store?

A Store (like Pinia) is an entity holding state and business logic that isn't bound to your Component tree. In other words, it hosts global state. It's a bit like a component that is always there and that everybody can read off and write to. It has three concepts, the state, getters and actions and it's safe to assume these concepts are the equivalent of data, computed and methods in components.

Installation

yarn add pinia

# or with pnpm
pnpm install pinia

How to use

Create folder stores and create counter.js on it.

Example

// stores/counter.js 
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => {
    return { count: 0 }
  },
  // could also be defined as
  // state: () => ({ count: 0 })
  actions: {
    increment() {
      this.count++
    },
  },
})

More advance use case:

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

  return { count, increment }
})

Example usage

<script setup>
import { useCounterStore } from '@/stores/counter'

const counter = useCounterStore()

counter.count++
// with autocompletion ✨
counter.$patch({ count: counter.count + 1 })
// or using an action instead
counter.increment()
</script>

<template>
  <!-- Access the state directly from the store -->
  <div>Current Count: {{ counter.count }}</div>
</template>

Example with Pocketbase

store/auth.ts

import type { BaseAuthStore } from 'pocketbase'
import PocketBase from 'pocketbase'
import { defineStore } from 'pinia'
import { backendUrl } from '~/global'

export const useAuth = defineStore('useAuth', {
  state: () => ({
    loggedIn: false,
    role: '',
    authData: undefined as BaseAuthStore | undefined,
    pb: undefined as PocketBase | undefined,
    err: '' as unknown | unknown,
  }),
  actions: {
    async init() {
      try {
        if (!this.pb) this.pb = new PocketBase(backendUrl)
      } catch (error) {
        this.loggedIn = false
        this.err = error
      }
    },

    async login(email: string, password: string, asAdmin: boolean) {
      if (this.pb) {
        try {
          if (asAdmin) {
            await this.pb.admins.authWithPassword(email, password)
            this.role = 'admin'
          } else
            await this.pb
              .collection('users')
              .authWithPassword(email, password, { expand: 'role' })

          this.authData = this.pb.authStore
          if (this.authData.model) this.role = this.authData.model.role
          this.loggedIn = true
        } catch (e) {
          this.err = e
          this.loggedIn = false
        }
      } else this.loggedIn = false
    },

    logout() {
      if (this.authData) this.authData.clear()
    },
  },
})

Middleware example

middleware/auth.ts

import { useAuth } from '~/store/auth'

export default defineNuxtRouteMiddleware((to, from) => {
  const auth = useAuth()
  if (!auth.loggedIn) {
    return navigateTo('/login')
  }
})

Login example

login.vue

<script setup lang="ts">
import { useAuth } from '~/store/auth'

onMounted(async () => {
  await authStore.init()
})

// This is where you would send the form data to the server
const onSubmit = handleSubmit(async (values) => {
  // here you have access to the validated form values
  console.log('auth-success', values)

  try {
    await authStore.login(values.email, values.password, false)

    if (authStore.loggedIn) {
      console.log('Logged in with role', authStore.role)

      toaster.clearAll()
      toaster.show({
        title: 'Success',
        message: `Welcome back!`,
        color: 'success',
        icon: 'ph:user-circle-fill',
        closable: true,
      })

      router.push('/input')
    }
  } catch (error: any) {
    console.log('Login exception: ', error)
    setFieldError('password', 'Invalid email or password')
    return
  }
})
</script>

Example nuxt page using middleware

input.vue

<template>
  <p class="text-lg">Input goes here</p>
</template>

<script setup lang="ts">
definePageMeta({
  title: 'Input',
  layout: 'collapse',
  middleware: ['auth'], // <== ~/middleware/auth.ts
})
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment