Step 10 of 30
setup stores, state/getters/actions, where state belongs (component vs store), router guard integration
state ที่แชร์ข้ามหน้า (session, domain data) — เก็บที่เดียวใน Pinia store ไม่ใช่ prop-drilling
Pinia is Vue's official state management library: typed stores that hold shared domain state — user session, server data, UI-wide flags — outside the component tree.
Props and events work for parent-child flows, but session state used by the navbar, the router guard, and five pages is not "owned" by any of them. A Pinia store gives that state one addressable home with devtools tracking, typed access, and testability. It also fits an API-backed mental model: stores are where server state lands after Eden Treaty calls.
// stores/auth.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useAuthStore = defineStore('auth', () => {
// state
const token = ref<string | null>(localStorage.getItem('token'))
const user = ref<User | null>(null)
// getters
const isAuthenticated = computed(() => token.value !== null)
// actions
async function login(email: string, password: string) {
const { token: t, user: u } = await api.auth.login({ email, password })
token.value = t
user.value = u
localStorage.setItem('token', t)
}
function logout() {
token.value = null
user.value = null
localStorage.removeItem('token')
}
return { token, user, isAuthenticated, login, logout }
})
This setup-store style is plain Composition API — ref is state, computed is getters, functions are actions.
<script setup lang="ts">
import { useAuthStore } from '@/stores/auth'
const auth = useAuthStore() // call inside setup
</script>
<template>
<nav v-if="auth.isAuthenticated">{{ auth.user?.email }}</nav>
</template>
In the router guard:
const auth = useAuthStore() // after pinia is installed
if (to.meta.requiresAuth && !auth.isAuthenticated) { ... }
| State | Where |
|---|---|
| Input value of one form | Component ref |
| Modal open/closed for one page | Component ref |
| Session, current user | Pinia store |
| Tasks fetched from the API | Pinia store (or composable + store) |
| Server data with caching/invalidation | Composable over a store, or a query library |
Rule: component state is private; store state is shared by name.
Split by domain — auth, tasks, notifications. Stores can import and call each other (useTasksStore() inside an action), so cross-domain workflows stay explicit.
Loading diagram...
fullName should be a computed getter, not a second ref kept in sync by hand.useAuthStore() at module top-level. Pinia is not installed yet. Call it inside setup, actions, or after app install.localStorage explicitly, and rehydrate on startup.