#vue #javascript #frontend

Day 12: State Management with Pinia

Welcome to Day 12! When your app grows, passing props down 10 levels (“prop drilling”) becomes a nightmare. We need a Global Store.

Enter Pinia, the official state management library for Vue. It feels just like the Composition API.

Setup

npm install pinia

main.ts

import { createPinia } from 'pinia'
app.use(createPinia())

Defining a Store

A store is a bucket of logic. Variables are state, Computed are getters, and Functions are actions.

stores/counter.ts

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

// Name 'counter' must be unique
export const useCounterStore = defineStore('counter', () => {
  // State
  const count = ref(0)

  // Getters
  const doubleCount = computed(() => count.value * 2)

  // Actions
  function increment() {
    count.value++
  }

  return { count, doubleCount, increment }
})

Using the Store

Using it is incredibly simple. Just import it and call it.

App.vue

<script setup lang="ts">
import { useCounterStore } from './stores/counter'
import { storeToRefs } from 'pinia'

const store = useCounterStore()

// Destructuring state directly breaks reactivity!
// Use storeToRefs for state/getters
const { count, doubleCount } = storeToRefs(store)

// Actions can be destructured directly
const { increment } = store
</script>

<template>
  <h1>Count: {{ count }}</h1>
  <h2>Double: {{ doubleCount }}</h2>
  <button @click="increment">Add</button>
</template>

Note: This count is shared across the entire app. If Component B uses useCounterStore(), it sees the exact same count.

Challenge for Day 12

  1. Create a useUserStore.
  2. State: username (string), isLoggedIn (boolean).
  3. Actions: login(name), logout().
  4. In App.vue, show “Welcome, {{ username }}” if logged in, otherwise show a “Login” button.

Solution:

stores/user.ts

import { defineStore } from 'pinia'
import { ref } from 'vue'

export const useUserStore = defineStore('user', () => {
  const username = ref('')
  const isLoggedIn = ref(false)

  function login(name: string) {
    username.value = name
    isLoggedIn.value = true
  }

  function logout() {
    username.value = ''
    isLoggedIn.value = false
  }

  return { username, isLoggedIn, login, logout }
})

App.vue

<script setup lang="ts">
import { useUserStore } from './stores/user'
import { storeToRefs } from 'pinia'

const store = useUserStore()
const { username, isLoggedIn } = storeToRefs(store)
</script>

<template>
  <div v-if="isLoggedIn">
    <p>Welcome, {{ username }}</p>
    <button @click="store.logout()">Logout</button>
  </div>
  <div v-else>
    <button @click="store.login('Jack')">Login as Jack</button>
  </div>
</template>

You have successfully lifted state up! Tomorrow, we look at how to handle Async operations (API calls) within this architecture.