Back to Rules
Vue.js89% popularity

Vue 3 Pinia State Management

Implement centralized state management with Pinia for Vue 3 applications.

Eduardo San Martin MoroteUpdated Mar 1, 2024

Overview

Pinia has become the official state management solution for Vue 3, replacing Vuex with a simpler, type-safe architecture that better leverages Vue 3's Composition API. Unlike Vuex's strict action-mutation-commit pattern, Pinia allows direct state mutations in actions while maintaining reactivity, reducing boilerplate significantly. The setup store syntax in Pinia mirrors the Composition API pattern, allowing colocated state, getters, and actions in a single function. This approach provides excellent TypeScript inference without decorators or complex type gymnastics. The returned object from setup() automatically becomes reactive, and functions can be passed as store actions. Getters in Pinia are computed properties that automatically track their dependencies. They can access other getters using this and can perform complex transformations on state. Unlike Vuex mutations which required separate getter objects, Pinia getters are defined alongside state, improving code organization for complex stores. Actions in Pinia are asynchronous by default, handling API calls and other async operations naturally. They can modify state directly, call other stores, and return values that callers can await. This flexibility, combined with Pinia's devtools integration for time-travel debugging and action history, makes debugging significantly easier than with Vuex.

Code Example

.vuerules
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import type { User, Project } from '@/types';
import { userApi } from '@/api/users';
import { useProjectStore } from './projects';

export const useUserStore = defineStore('user', () => {
  // State
  const currentUser = ref<User | null>(null);
  const userProjects = ref<Project[]>([]);
  const isLoading = ref(false);
  const error = ref<string | null>(null);

  // Getters
  const isAuthenticated = computed(() => !!currentUser.value);
  const userInitials = computed(() => {
    if (!currentUser.value) return '';
    return currentUser.value.name
      .split(' ')
      .map(n => n[0])
      .join('')
      .toUpperCase();
  });

  const recentProjects = computed(() => {
    return userProjects.value
      .sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime())
      .slice(0, 5);
  });

  // Actions
  async function login(email: string, password: string) {
    isLoading.value = true;
    error.value = null;

    try {
      const { user, token } = await userApi.login({ email, password });
      currentUser.value = user;
      localStorage.setItem('auth_token', token);
      return user;
    } catch (e) {
      error.value = e instanceof Error ? e.message : 'Login failed';
      throw e;
    } finally {
      isLoading.value = false;
    }
  }

  async function fetchUserProjects() {
    if (!currentUser.value) return;

    isLoading.value = true;
    try {
      userProjects.value = await userApi.getProjects(currentUser.value.id);
    } finally {
      isLoading.value = false;
    }
  }

  function logout() {
    currentUser.value = null;
    userProjects.value = [];
    localStorage.removeItem('auth_token');
  }

  function updateProfile(updates: Partial<User>) {
    if (!currentUser.value) return;
    currentUser.value = { ...currentUser.value, ...updates };
  }

  return {
    // State
    currentUser,
    userProjects,
    isLoading,
    error,
    // Getters
    isAuthenticated,
    userInitials,
    recentProjects,
    // Actions
    login,
    fetchUserProjects,
    logout,
    updateProfile,
  };
});

// Store with persistence
export const useSettingsStore = defineStore('settings', () => {
  const theme = ref<'light' | 'dark'>('dark');
  const sidebarCollapsed = ref(false);

  function toggleTheme() {
    theme.value = theme.value === 'light' ? 'dark' : 'light';
  }

  return { theme, sidebarCollapsed, toggleTheme };
});

More Vue.js Rules

VUE
91%

Vue 3 Composition API Patterns

Prefer Composition API with script setup for better TypeScript support and reusable logic patterns.

vue3composition-apitypescript
<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue';
import { useAuth } from '@/composables/useAuth';
import { useTodos } f...
Feb 14, 2024by Evan You
View Rule
VUE
85%

Vue 3 Routing with Vue Router 4

Implement navigation guards, lazy loading, and advanced routing patterns in Vue 3.

vue3vue-routerrouting
<script setup lang="ts">
import { useRouter, useRoute } from 'vue-router';
import { computed, onMounted } from 'vue';
import { useAuthStore } from '@/...
Feb 5, 2024by Eduardo San Martin Morote
View Rule
VUE
93%

Vue 3 Composables Pattern

Extract and reuse stateful logic with Vue 3 Composables using the Composition API.

vue3composablescomposition-api
import { ref, computed, watch, onMounted, onUnmounted } from 'vue';
import { useLocalStorage } from './useLocalStorage';
import { useFetch } from './u...
Mar 18, 2024by Evan You
View Rule