Vue Pinia Best Practices

Vue Pinia Best Practices automation and integration

Vue Pinia Best Practices is a community skill for managing application state with Pinia in Vue applications, covering store definition, getters, actions, composition, and testing strategies.

What Is This?

Overview

Vue Pinia Best Practices provides patterns for building state management layers with Pinia in Vue 3 applications. It covers store definition using both Options and Setup syntax for different organizational preferences, getters for computed derived state that caches based on dependencies, actions for async operations with error handling and loading states, and store composition for sharing logic between stores. The skill enables developers to build predictable, testable state management that scales with application complexity.

Who Should Use This

This skill serves Vue developers implementing global state management with Pinia, teams migrating from Vuex to Pinia for simpler store patterns, and engineers building modular stores for feature-based application architecture.

Why Use It?

Problems It Solves

Managing shared state across components without a centralized store leads to prop drilling and event chains. Async data loading with error and loading tracking needs structured patterns. Testing component logic that depends on global state requires store mocking capabilities. Splitting state into modular stores that reference each other demands composition patterns.

Core Highlights

Store factory creates typed state containers with reactive properties and actions. Getter system computes derived state with automatic caching. Action handlers manage async operations with error and loading state. Store composition enables cross-store access within actions.

How to Use It?

Basic Usage

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

export const useAuthStore =
  defineStore('auth', () => {
    const user = ref<{
      id: string;
      name: string;
      email: string;
    } | null>(null);
    const token = ref<string | null>(
      null);
    const loading = ref(false);

    const isAuthenticated = computed(
      () => !!token.value);
    const userName = computed(
      () => user.value?.name ?? '');

    async function login(
      email: string,
      password: string
    ) {
      loading.value = true;
      try {
        const res = await fetch(
          '/api/auth/login', {
            method: 'POST',
            headers: { 'Content-Type':
              'application/json' },
            body: JSON.stringify({
              email, password }),
          });
        const data = await res.json();
        user.value = data.user;
        token.value = data.token;
      } finally {
        loading.value = false;
      }
    }

    function logout() {
      user.value = null;
      token.value = null;
    }

    return { user, token, loading,
      isAuthenticated, userName,
      login, logout };
  });

Real-World Examples

import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import { useAuthStore } from
  './auth';

interface Task {
  id: string;
  title: string;
  completed: boolean;
}

export const useTaskStore =
  defineStore('tasks', () => {
    const tasks = ref<Task[]>([]);
    const loading = ref(false);
    const error = ref<string | null>(
      null);

    const pending = computed(
      () => tasks.value.filter(
        (t) => !t.completed));
    const completedCount = computed(
      () => tasks.value.filter(
        (t) => t.completed).length);

    async function fetchTasks() {
      const auth = useAuthStore();
      if (!auth.isAuthenticated) return;
      loading.value = true;
      error.value = null;
      try {
        const res = await fetch(
          '/api/tasks', {
            headers: {
              Authorization:
                `Bearer ${auth.token}`,
            },
          });
        tasks.value = await res.json();
      } catch (e: unknown) {
        error.value =
          (e as Error).message;
      } finally {
        loading.value = false;
      }
    }

    async function toggle(id: string) {
      const task = tasks.value.find(
        (t) => t.id === id);
      if (task) {
        task.completed =
          !task.completed;
      }
    }

    return { tasks, loading, error,
      pending, completedCount,
      fetchTasks, toggle };
  });

Advanced Tips

Use storeToRefs to destructure store state and getters while preserving reactivity in components. Implement Pinia plugins for persistence, logging, or error tracking. Use $reset on options stores to restore initial state during logout or testing.

When to Use It?

Use Cases

Build an authentication store that manages tokens, user data, and session state across the application. Create a task management store with filtered views and optimistic updates. Implement a shopping cart store with computed totals and persistent state across page refreshes.

Related Topics

Vue state management, Pinia stores, reactive programming, Composition API, and application architecture.

Important Notes

Requirements

Vue 3 with Pinia installed and configured in the app instance. TypeScript recommended for typed store definitions. Composition API knowledge for setup-style stores.

Usage Recommendations

Do: use setup syntax for stores that benefit from Composition API patterns like composable reuse. Access other stores inside actions rather than in state or getters to avoid circular dependencies. Use getters for derived state instead of computing values in components.

Don't: create a single massive store when feature-based store splitting improves maintainability. Mutate store state directly from components when actions encapsulate the logic with error handling. Use watchers on store state in components when getters can provide the derived value reactively.

Limitations

Pinia stores do not persist state across page reloads without plugins. Setup-style stores do not support $reset without manual implementation. Store composition can create implicit dependencies that are not visible from the store definition alone.