+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 181 of 355

🍍 Pinia: Modern Vue State Management

Master pinia: modern vue state management in TypeScript with practical examples, best practices, and real-world applications 🚀

🚀Intermediate
25 min read

Prerequisites

  • Basic understanding of JavaScript 📝
  • TypeScript installation ⚡
  • VS Code or preferred IDE 💻
  • Vue 3 fundamentals 🟢

What you'll learn

  • Understand Pinia store fundamentals 🎯
  • Apply Pinia in real Vue projects 🏗️
  • Debug common state management issues 🐛
  • Write type-safe state management code ✨

🎯 Introduction

Welcome to the exciting world of Pinia! 🎉 Say goodbye to complex state management and hello to the modern, intuitive way to handle your Vue 3 applications’ state.

Think of Pinia as your app’s smart assistant 🤖 - it keeps track of everything important, shares information between components, and makes sure your data stays organized and accessible. Whether you’re building a shopping app 🛒, a social media platform 📱, or a dashboard 📊, Pinia will make your state management journey smooth and enjoyable!

By the end of this tutorial, you’ll be creating stores like a pro and managing state with confidence! Let’s dive into the pineapple goodness! 🍍

📚 Understanding Pinia

🤔 What is Pinia?

Pinia is like having a super-organized friend 🧠 who remembers everything for you! Think of it as a central warehouse 🏬 where all your app’s important data lives, perfectly organized and easily accessible from any part of your application.

In Vue terms, Pinia is the official state management library that provides:

  • Intuitive API - Simple and straightforward
  • 🚀 Great Performance - Lightweight and fast
  • 🛡️ Type Safety - Full TypeScript support
  • 🔧 DevTools Support - Excellent debugging experience

💡 Why Use Pinia Over Vuex?

Here’s why developers are switching to Pinia:

  1. Simpler API 🎯: No mutations, actions are just functions
  2. Better TypeScript 💙: Native TypeScript support without extra setup
  3. Modular Design 🧩: Each store is independent and focused
  4. Hot Module Replacement 🔥: Better development experience
  5. Smaller Bundle 📦: Less code means faster apps

Real-world example: Imagine building a todo app 📝. With Pinia, your todo store is just a simple object with state, getters, and actions - no confusing boilerplate!

🔧 Basic Syntax and Usage

📦 Installation and Setup

Let’s get Pinia ready in your Vue project:

# 🍍 Install Pinia
npm install pinia
// 🏗️ main.ts - Setting up Pinia
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';

const app = createApp(App);
const pinia = createPinia(); // 🍍 Create our pineapple store factory

app.use(pinia); // 🎯 Tell Vue to use Pinia
app.mount('#app');

🏪 Creating Your First Store

Here’s the magic of Pinia stores:

// 🍍 stores/counter.ts - Simple counter store
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
  // 📊 State - your data lives here
  state: () => ({
    count: 0,
    name: 'Awesome Counter! 🚀'
  }),
  
  // 📖 Getters - computed values
  getters: {
    // 🎯 Double the count
    doubleCount: (state) => state.count * 2,
    
    // 🎨 Formatted display
    displayText: (state) => `${state.name}: ${state.count} 🎉`
  },
  
  // 🎬 Actions - methods that can modify state
  actions: {
    // ➕ Increment counter
    increment() {
      this.count++; // 🚀 Direct mutation - so simple!
      console.log(`📈 Count increased to ${this.count}!`);
    },
    
    // 🎯 Set to specific value
    setCount(value: number) {
      this.count = value;
      console.log(`🎯 Count set to ${value}!`);
    },
    
    // 🔄 Reset to zero
    reset() {
      this.count = 0;
      console.log('🔄 Counter reset! Starting fresh! ✨');
    }
  }
});

💡 Pro Tip: Notice how clean this is! No mutations, no commit() calls - just simple, direct state updates!

💡 Practical Examples

🛒 Example 1: Shopping Cart Store

Let’s build something real and fun:

// 🛍️ stores/cart.ts - Shopping cart with type safety
import { defineStore } from 'pinia';

interface Product {
  id: string;
  name: string;
  price: number;
  emoji: string;
  inStock: boolean;
}

interface CartItem extends Product {
  quantity: number;
}

export const useCartStore = defineStore('cart', {
  state: () => ({
    items: [] as CartItem[],
    isLoading: false,
    discountCode: '' as string
  }),
  
  getters: {
    // 💰 Calculate total price
    totalPrice: (state) => {
      return state.items.reduce((total, item) => 
        total + (item.price * item.quantity), 0
      );
    },
    
    // 📦 Total items count
    itemCount: (state) => {
      return state.items.reduce((count, item) => 
        count + item.quantity, 0
      );
    },
    
    // 🎉 Formatted summary
    cartSummary: (state) => {
      if (state.items.length === 0) return 'Your cart is empty! 🛒';
      return `🛒 ${state.itemCount} items • $${state.totalPrice.toFixed(2)}`;
    },
    
    // 🔍 Check if product is in cart
    isInCart: (state) => {
      return (productId: string) => 
        state.items.some(item => item.id === productId);
    }
  },
  
  actions: {
    // ➕ Add product to cart
    async addToCart(product: Product) {
      this.isLoading = true;
      
      try {
        const existingItem = this.items.find(item => item.id === product.id);
        
        if (existingItem) {
          // 📈 Increase quantity
          existingItem.quantity++;
          console.log(`📈 Updated ${product.emoji} ${product.name} quantity!`);
        } else {
          // 🆕 Add new item
          this.items.push({ ...product, quantity: 1 });
          console.log(`🎉 Added ${product.emoji} ${product.name} to cart!`);
        }
      } catch (error) {
        console.error('❌ Failed to add item:', error);
      } finally {
        this.isLoading = false;
      }
    },
    
    // 🗑️ Remove item from cart
    removeFromCart(productId: string) {
      const index = this.items.findIndex(item => item.id === productId);
      if (index > -1) {
        const item = this.items[index];
        this.items.splice(index, 1);
        console.log(`🗑️ Removed ${item.emoji} ${item.name} from cart!`);
      }
    },
    
    // 🔄 Update quantity
    updateQuantity(productId: string, quantity: number) {
      const item = this.items.find(item => item.id === productId);
      if (item) {
        if (quantity <= 0) {
          this.removeFromCart(productId);
        } else {
          item.quantity = quantity;
          console.log(`🔄 Updated quantity to ${quantity}!`);
        }
      }
    },
    
    // 🧹 Clear entire cart
    clearCart() {
      this.items = [];
      this.discountCode = '';
      console.log('🧹 Cart cleared! Ready for new adventures! ✨');
    }
  }
});

🎮 Example 2: User Authentication Store

Let’s handle user authentication with style:

// 👤 stores/auth.ts - User authentication store
import { defineStore } from 'pinia';

interface User {
  id: string;
  username: string;
  email: string;
  avatar: string;
  role: 'user' | 'admin';
}

interface LoginCredentials {
  email: string;
  password: string;
}

export const useAuthStore = defineStore('auth', {
  state: () => ({
    user: null as User | null,
    token: localStorage.getItem('token') || '',
    isLoading: false,
    error: '' as string
  }),
  
  getters: {
    // 🔐 Check if user is logged in
    isAuthenticated: (state) => !!state.token && !!state.user,
    
    // 👑 Check if user is admin
    isAdmin: (state) => state.user?.role === 'admin',
    
    // 🎨 User display name
    displayName: (state) => {
      if (!state.user) return 'Guest 👋';
      return `${state.user.username} ${state.user.avatar}`;
    },
    
    // 🚨 Has authentication error
    hasError: (state) => !!state.error
  },
  
  actions: {
    // 🔑 Login user
    async login(credentials: LoginCredentials) {
      this.isLoading = true;
      this.error = '';
      
      try {
        // 🌐 Simulated API call
        const response = await fetch('/api/login', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(credentials)
        });
        
        if (!response.ok) {
          throw new Error('Invalid credentials! 🚫');
        }
        
        const data = await response.json();
        
        // 🎉 Store user data
        this.user = data.user;
        this.token = data.token;
        localStorage.setItem('token', data.token);
        
        console.log(`🎉 Welcome back, ${this.user.username}! ${this.user.avatar}`);
        
      } catch (error) {
        this.error = error instanceof Error ? error.message : 'Login failed! 😰';
        console.error('❌ Login error:', this.error);
      } finally {
        this.isLoading = false;
      }
    },
    
    // 📝 Register new user
    async register(userData: Omit<User, 'id'> & { password: string }) {
      this.isLoading = true;
      this.error = '';
      
      try {
        const response = await fetch('/api/register', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(userData)
        });
        
        if (!response.ok) {
          throw new Error('Registration failed! 😰');
        }
        
        const data = await response.json();
        console.log('🎉 Registration successful! Welcome aboard! 🚀');
        
        // 🔑 Auto-login after registration
        await this.login({
          email: userData.email,
          password: userData.password
        });
        
      } catch (error) {
        this.error = error instanceof Error ? error.message : 'Registration failed!';
      } finally {
        this.isLoading = false;
      }
    },
    
    // 🚪 Logout user
    logout() {
      this.user = null;
      this.token = '';
      this.error = '';
      localStorage.removeItem('token');
      console.log('👋 Logged out successfully! See you soon! ✨');
    },
    
    // 🔄 Clear error
    clearError() {
      this.error = '';
    }
  }
});

🚀 Advanced Concepts

🧙‍♂️ Composition API Style Stores

For the modern Vue developers, here’s the Composition API approach:

// 🎯 stores/todos.ts - Composition API style
import { ref, computed } from 'vue';
import { defineStore } from 'pinia';

interface Todo {
  id: string;
  text: string;
  completed: boolean;
  emoji: string;
  priority: 'low' | 'medium' | 'high';
}

export const useTodosStore = defineStore('todos', () => {
  // 📊 State using refs
  const todos = ref<Todo[]>([]);
  const filter = ref<'all' | 'active' | 'completed'>('all');
  const isLoading = ref(false);
  
  // 📖 Getters using computed
  const completedTodos = computed(() => 
    todos.value.filter(todo => todo.completed)
  );
  
  const activeTodos = computed(() => 
    todos.value.filter(todo => !todo.completed)
  );
  
  const filteredTodos = computed(() => {
    switch (filter.value) {
      case 'active': return activeTodos.value;
      case 'completed': return completedTodos.value;
      default: return todos.value;
    }
  });
  
  const stats = computed(() => ({
    total: todos.value.length,
    completed: completedTodos.value.length,
    active: activeTodos.value.length,
    completionRate: todos.value.length > 0 
      ? Math.round((completedTodos.value.length / todos.value.length) * 100)
      : 0
  }));
  
  // 🎬 Actions - regular functions
  const addTodo = (text: string, emoji: string = '📝') => {
    const newTodo: Todo = {
      id: Date.now().toString(),
      text,
      completed: false,
      emoji,
      priority: 'medium'
    };
    
    todos.value.push(newTodo);
    console.log(`✅ Added: ${emoji} ${text}`);
  };
  
  const toggleTodo = (id: string) => {
    const todo = todos.value.find(t => t.id === id);
    if (todo) {
      todo.completed = !todo.completed;
      const status = todo.completed ? 'completed' : 'active';
      console.log(`🔄 Todo ${status}: ${todo.emoji} ${todo.text}`);
    }
  };
  
  const deleteTodo = (id: string) => {
    const index = todos.value.findIndex(t => t.id === id);
    if (index > -1) {
      const todo = todos.value[index];
      todos.value.splice(index, 1);
      console.log(`🗑️ Deleted: ${todo.emoji} ${todo.text}`);
    }
  };
  
  const setFilter = (newFilter: typeof filter.value) => {
    filter.value = newFilter;
    console.log(`🔍 Filter set to: ${newFilter}`);
  };
  
  // 🎯 Return everything (like export)
  return {
    // State
    todos,
    filter,
    isLoading,
    // Getters
    completedTodos,
    activeTodos,
    filteredTodos,
    stats,
    // Actions
    addTodo,
    toggleTodo,
    deleteTodo,
    setFilter
  };
});

🏗️ Store Composition and Plugins

Advanced patterns for power users:

// 🔌 Store plugins for extra functionality
import { defineStore } from 'pinia';

// 📊 Analytics plugin
const analyticsPlugin = (context: any) => {
  // 📝 Track all actions
  context.store.$onAction(({ name, args }) => {
    console.log(`📊 Action: ${name}`, args);
    // Send to analytics service
  });
};

// 💾 Persistence plugin
const persistencePlugin = (context: any) => {
  const storeId = context.store.$id;
  
  // 📥 Load from localStorage
  const saved = localStorage.getItem(`pinia-${storeId}`);
  if (saved) {
    context.store.$patch(JSON.parse(saved));
  }
  
  // 💾 Save on changes
  context.store.$subscribe((mutation, state) => {
    localStorage.setItem(`pinia-${storeId}`, JSON.stringify(state));
  });
};

// 🏪 Store that uses other stores
export const useAppStore = defineStore('app', {
  state: () => ({
    theme: 'light' as 'light' | 'dark',
    notifications: [] as string[]
  }),
  
  actions: {
    // 🎨 Toggle theme and notify user
    toggleTheme() {
      this.theme = this.theme === 'light' ? 'dark' : 'light';
      
      // 🔔 Use auth store to check if user is logged in
      const authStore = useAuthStore();
      if (authStore.isAuthenticated) {
        this.addNotification(`🎨 Theme changed to ${this.theme} mode!`);
      }
    },
    
    // 🔔 Add notification
    addNotification(message: string) {
      this.notifications.push(message);
      setTimeout(() => {
        this.removeNotification(message);
      }, 3000);
    },
    
    // 🗑️ Remove notification
    removeNotification(message: string) {
      const index = this.notifications.indexOf(message);
      if (index > -1) {
        this.notifications.splice(index, 1);
      }
    }
  }
});

⚠️ Common Pitfalls and Solutions

😱 Pitfall 1: Destructuring Store State

// ❌ Wrong - loses reactivity!
const { count, name } = useCounterStore();
// These won't update when store changes! 😰

// ✅ Correct - preserve reactivity
import { storeToRefs } from 'pinia';
const store = useCounterStore();
const { count, name } = storeToRefs(store); // 🎯 Now they're reactive!

// ✅ Alternative - use the store directly
const store = useCounterStore();
// Use store.count, store.name in templates

🤯 Pitfall 2: Calling Actions in Getters

// ❌ Wrong - actions in getters!
getters: {
  processedData: (state) => {
    this.loadData(); // 💥 Don't call actions in getters!
    return state.data.map(item => item.processed);
  }
}

// ✅ Correct - keep getters pure
getters: {
  processedData: (state) => {
    return state.data.map(item => item.processed); // ✨ Pure function
  }
}

// 🎯 Call actions in components or other actions
actions: {
  async initializeData() {
    await this.loadData();
    // Now processedData getter will work with loaded data
  }
}

🚫 Pitfall 3: Forgetting Async/Await

// ❌ Wrong - not handling promises
actions: {
  loadUser(id: string) {
    fetchUser(id).then(user => {
      this.user = user; // 😰 This might not work as expected
    });
  }
}

// ✅ Correct - proper async handling
actions: {
  async loadUser(id: string) {
    try {
      this.isLoading = true;
      this.user = await fetchUser(id); // 🎯 Clean and predictable
      console.log('✅ User loaded successfully!');
    } catch (error) {
      console.error('❌ Failed to load user:', error);
      this.error = 'Failed to load user';
    } finally {
      this.isLoading = false;
    }
  }
}

🛠️ Best Practices

  1. 🎯 One Responsibility: Each store should handle one domain (users, cart, etc.)
  2. 📝 Descriptive Names: Use clear, descriptive names for stores and actions
  3. 🛡️ Type Everything: Define interfaces for your state and data structures
  4. 🔄 Handle Loading States: Always show users when operations are in progress
  5. 🚨 Error Handling: Catch and handle errors gracefully
  6. 🧪 Test Your Stores: Write tests for your store logic
  7. 📊 Use DevTools: Leverage Vue DevTools for debugging
  8. 💾 Persist Important Data: Save critical state to localStorage when needed

🧪 Hands-On Exercise

🎯 Challenge: Build a Recipe Book Store

Create a complete recipe management system with Pinia:

📋 Requirements:

  • ✅ Store recipes with ingredients, instructions, and ratings
  • 🏷️ Categories (breakfast, lunch, dinner, dessert)
  • ❤️ Favorites system
  • 🔍 Search and filter functionality
  • 📊 Statistics (total recipes, average rating)
  • 🎨 Each recipe needs an emoji!

🚀 Bonus Points:

  • Add ingredient shopping list generator
  • Implement recipe sharing functionality
  • Create meal planning features
  • Add cooking timer integration

💡 Solution

🔍 Click to see solution
// 🍳 stores/recipes.ts - Complete recipe management
import { defineStore } from 'pinia';

interface Recipe {
  id: string;
  title: string;
  emoji: string;
  category: 'breakfast' | 'lunch' | 'dinner' | 'dessert';
  ingredients: string[];
  instructions: string[];
  prepTime: number; // in minutes
  cookTime: number;
  servings: number;
  rating: number; // 1-5 stars
  isFavorite: boolean;
  tags: string[];
  difficulty: 'easy' | 'medium' | 'hard';
  createdAt: Date;
}

export const useRecipesStore = defineStore('recipes', {
  state: () => ({
    recipes: [] as Recipe[],
    searchQuery: '',
    selectedCategory: 'all' as Recipe['category'] | 'all',
    showFavoritesOnly: false,
    isLoading: false,
    currentRecipe: null as Recipe | null
  }),
  
  getters: {
    // 🔍 Filtered recipes based on search and filters
    filteredRecipes: (state) => {
      let filtered = state.recipes;
      
      // 🔍 Apply search filter
      if (state.searchQuery) {
        filtered = filtered.filter(recipe =>
          recipe.title.toLowerCase().includes(state.searchQuery.toLowerCase()) ||
          recipe.ingredients.some(ingredient =>
            ingredient.toLowerCase().includes(state.searchQuery.toLowerCase())
          )
        );
      }
      
      // 🏷️ Apply category filter
      if (state.selectedCategory !== 'all') {
        filtered = filtered.filter(recipe => recipe.category === state.selectedCategory);
      }
      
      // ❤️ Apply favorites filter
      if (state.showFavoritesOnly) {
        filtered = filtered.filter(recipe => recipe.isFavorite);
      }
      
      return filtered;
    },
    
    // ❤️ Favorite recipes
    favoriteRecipes: (state) => state.recipes.filter(recipe => recipe.isFavorite),
    
    // 📊 Recipe statistics
    stats: (state) => {
      const totalRecipes = state.recipes.length;
      const averageRating = totalRecipes > 0
        ? state.recipes.reduce((sum, recipe) => sum + recipe.rating, 0) / totalRecipes
        : 0;
      
      const categoryStats = state.recipes.reduce((stats, recipe) => {
        stats[recipe.category] = (stats[recipe.category] || 0) + 1;
        return stats;
      }, {} as Record<Recipe['category'], number>);
      
      return {
        total: totalRecipes,
        favorites: state.recipes.filter(r => r.isFavorite).length,
        averageRating: Math.round(averageRating * 10) / 10,
        byCategory: categoryStats,
        totalCookTime: state.recipes.reduce((total, recipe) => 
          total + recipe.prepTime + recipe.cookTime, 0
        )
      };
    },
    
    // 🛒 Shopping list from selected recipes
    shoppingList: (state) => {
      const selectedRecipes = state.recipes.filter(recipe => recipe.isFavorite);
      const allIngredients = selectedRecipes.flatMap(recipe => recipe.ingredients);
      
      // 📝 Remove duplicates and sort
      return [...new Set(allIngredients)].sort();
    },
    
    // 🎯 Recipe recommendations
    recommendedRecipes: (state) => {
      return state.recipes
        .filter(recipe => recipe.rating >= 4)
        .sort((a, b) => b.rating - a.rating)
        .slice(0, 5);
    }
  },
  
  actions: {
    // ➕ Add new recipe
    addRecipe(recipeData: Omit<Recipe, 'id' | 'createdAt'>) {
      const newRecipe: Recipe = {
        ...recipeData,
        id: Date.now().toString(),
        createdAt: new Date()
      };
      
      this.recipes.push(newRecipe);
      console.log(`🍳 Added recipe: ${newRecipe.emoji} ${newRecipe.title}!`);
    },
    
    // ✏️ Update recipe
    updateRecipe(id: string, updates: Partial<Recipe>) {
      const recipe = this.recipes.find(r => r.id === id);
      if (recipe) {
        Object.assign(recipe, updates);
        console.log(`✏️ Updated recipe: ${recipe.emoji} ${recipe.title}!`);
      }
    },
    
    // 🗑️ Delete recipe
    deleteRecipe(id: string) {
      const index = this.recipes.findIndex(r => r.id === id);
      if (index > -1) {
        const recipe = this.recipes[index];
        this.recipes.splice(index, 1);
        console.log(`🗑️ Deleted recipe: ${recipe.emoji} ${recipe.title}!`);
      }
    },
    
    // ❤️ Toggle favorite
    toggleFavorite(id: string) {
      const recipe = this.recipes.find(r => r.id === id);
      if (recipe) {
        recipe.isFavorite = !recipe.isFavorite;
        const status = recipe.isFavorite ? 'added to' : 'removed from';
        console.log(`❤️ Recipe ${status} favorites: ${recipe.emoji} ${recipe.title}!`);
      }
    },
    
    // ⭐ Rate recipe
    rateRecipe(id: string, rating: number) {
      const recipe = this.recipes.find(r => r.id === id);
      if (recipe && rating >= 1 && rating <= 5) {
        recipe.rating = rating;
        console.log(`⭐ Rated ${recipe.emoji} ${recipe.title}: ${rating} stars!`);
      }
    },
    
    // 🔍 Set search query
    setSearchQuery(query: string) {
      this.searchQuery = query;
    },
    
    // 🏷️ Set category filter
    setCategoryFilter(category: Recipe['category'] | 'all') {
      this.selectedCategory = category;
    },
    
    // ❤️ Toggle favorites filter
    toggleFavoritesFilter() {
      this.showFavoritesOnly = !this.showFavoritesOnly;
    },
    
    // 📥 Load sample recipes
    loadSampleRecipes() {
      const sampleRecipes: Omit<Recipe, 'id' | 'createdAt'>[] = [
        {
          title: 'Perfect Pancakes',
          emoji: '🥞',
          category: 'breakfast',
          ingredients: ['2 cups flour', '2 eggs', '1 cup milk', '2 tbsp sugar'],
          instructions: ['Mix dry ingredients', 'Add wet ingredients', 'Cook on griddle'],
          prepTime: 10,
          cookTime: 15,
          servings: 4,
          rating: 5,
          isFavorite: true,
          tags: ['easy', 'family-friendly'],
          difficulty: 'easy'
        },
        {
          title: 'Chocolate Chip Cookies',
          emoji: '🍪',
          category: 'dessert',
          ingredients: ['2 cups flour', '1 cup butter', '1 cup brown sugar', '2 eggs', '1 cup chocolate chips'],
          instructions: ['Cream butter and sugar', 'Add eggs', 'Mix in flour', 'Fold in chocolate chips', 'Bake at 350°F'],
          prepTime: 15,
          cookTime: 12,
          servings: 24,
          rating: 4,
          isFavorite: false,
          tags: ['sweet', 'baking'],
          difficulty: 'medium'
        }
      ];
      
      sampleRecipes.forEach(recipe => this.addRecipe(recipe));
      console.log('📚 Sample recipes loaded! Happy cooking! 🍳');
    }
  }
});

🎓 Key Takeaways

You’ve mastered the art of Pinia! Here’s what you can now do:

  • Create modern stores with clean, intuitive syntax 💪
  • Manage complex state across your Vue applications 🎯
  • Handle async operations with proper error handling 🛡️
  • Use TypeScript for bulletproof state management 🚀
  • Compose stores and create reusable patterns 🧩
  • Debug effectively with Vue DevTools integration 🔍

Remember: Pinia makes state management enjoyable! It’s simple, powerful, and works beautifully with TypeScript. 🍍✨

🤝 Next Steps

Congratulations! 🎉 You’ve become a Pinia master!

Here’s what to do next:

  1. 💻 Build a real project using Pinia stores
  2. 🧪 Experiment with store composition patterns
  3. 📚 Explore Pinia plugins and advanced features
  4. 🌟 Share your Pinia creations with the community!

Keep building amazing Vue applications with type-safe state management! The pineapple power is now yours! 🍍🚀


Happy coding with Pinia! 🎉🍍✨