+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 180 of 355

๐Ÿ“˜ Vuex with TypeScript: State Management

Master vuex with typescript: 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 ๐Ÿ’ป

What you'll learn

  • Understand Vuex fundamentals with TypeScript ๐ŸŽฏ
  • Apply type-safe state management in real projects ๐Ÿ—๏ธ
  • Debug common Vuex TypeScript issues ๐Ÿ›
  • Write type-safe state management code โœจ

๐ŸŽฏ Introduction

Welcome to this exciting tutorial on Vuex with TypeScript! ๐ŸŽ‰ In this guide, weโ€™ll explore how to build type-safe, scalable state management for your Vue applications.

Youโ€™ll discover how Vuex combined with TypeScript can transform your Vue development experience. Whether youโ€™re building single-page applications ๐ŸŒ, complex dashboards ๐Ÿ“Š, or real-time interfaces โšก, understanding type-safe state management is essential for writing robust, maintainable code.

By the end of this tutorial, youโ€™ll feel confident building complex state management systems with full type safety! Letโ€™s dive in! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding Vuex with TypeScript

๐Ÿค” What is Vuex?

Vuex is like a centralized store for your Vue application ๐Ÿช. Think of it as a global state container that helps you manage data that needs to be shared across multiple components, with TypeScript adding compile-time safety to prevent bugs.

In TypeScript terms, Vuex provides a predictable state container with strong typing support โšก. This means you can:

  • โœจ Catch state-related errors at compile time
  • ๐Ÿš€ Get amazing IntelliSense and autocomplete
  • ๐Ÿ›ก๏ธ Ensure action and mutation type safety
  • ๐Ÿ“– Self-document your state structure

๐Ÿ’ก Why Use Vuex with TypeScript?

Hereโ€™s why developers love this combination:

  1. Type Safety ๐Ÿ”’: Catch state management errors at compile-time
  2. Better IDE Support ๐Ÿ’ป: Autocomplete for actions, mutations, and getters
  3. Code Documentation ๐Ÿ“–: Types serve as inline documentation
  4. Refactoring Confidence ๐Ÿ”ง: Change state structure without fear
  5. Team Collaboration ๐Ÿค: Clear contracts between components and store

Real-world example: Imagine building an e-commerce app ๐Ÿ›’. With Vuex + TypeScript, you can ensure your cart state, user authentication, and product data are all type-safe and consistent across your entire application.

๐Ÿ”ง Basic Syntax and Usage

๐Ÿ“ Simple Store Setup

Letโ€™s start with a basic typed Vuex store:

// ๐Ÿ‘‹ Hello, Vuex with TypeScript!
import { createStore, Store } from 'vuex';

// ๐ŸŽจ Define our state type
interface RootState {
  count: number;
  message: string;
  isLoading: boolean;
}

// ๐Ÿช Create our typed store
const store: Store<RootState> = createStore({
  state: {
    count: 0,
    message: "Welcome to Vuex! ๐ŸŽ‰",
    isLoading: false
  },
  
  // ๐Ÿ“Š Getters with type safety
  getters: {
    doubledCount: (state): number => state.count * 2,
    uppercaseMessage: (state): string => state.message.toUpperCase()
  },
  
  // ๐Ÿ”„ Mutations for synchronous state changes
  mutations: {
    INCREMENT(state): void {
      state.count++; // โœจ Type-safe mutation
    },
    SET_MESSAGE(state, payload: string): void {
      state.message = payload;
    },
    SET_LOADING(state, loading: boolean): void {
      state.isLoading = loading;
    }
  },
  
  // โšก Actions for asynchronous operations
  actions: {
    async fetchData({ commit }): Promise<void> {
      commit('SET_LOADING', true);
      // ๐ŸŒ Simulate API call
      await new Promise(resolve => setTimeout(resolve, 1000));
      commit('SET_MESSAGE', 'Data loaded! ๐ŸŽŠ');
      commit('SET_LOADING', false);
    }
  }
});

export default store;

๐Ÿ’ก Explanation: Notice how we define types for everything! The state interface ensures type safety, and TypeScript will warn us if we try to access properties that donโ€™t exist.

๐ŸŽฏ Using the Store in Components

Hereโ€™s how to use the typed store in Vue components:

// ๐Ÿ—๏ธ Component with typed store access
import { computed, ComputedRef } from 'vue';
import { useStore } from 'vuex';
import { RootState } from './store';

export default {
  setup() {
    // ๐ŸŽจ Get typed store instance
    const store = useStore<RootState>();
    
    // ๐Ÿ“Š Computed properties with full type safety
    const count: ComputedRef<number> = computed(() => store.state.count);
    const message: ComputedRef<string> = computed(() => store.state.message);
    const doubledCount: ComputedRef<number> = computed(() => store.getters.doubledCount);
    
    // ๐Ÿ”„ Typed action dispatching
    const increment = (): void => {
      store.commit('INCREMENT');
    };
    
    const updateMessage = (newMessage: string): void => {
      store.commit('SET_MESSAGE', newMessage);
    };
    
    const loadData = async (): Promise<void> => {
      await store.dispatch('fetchData');
    };
    
    return {
      count,
      message,
      doubledCount,
      increment,
      updateMessage,
      loadData
    };
  }
};

๐Ÿ’ก Practical Examples

๐Ÿ›’ Example 1: E-commerce Shopping Cart

Letโ€™s build a real shopping cart with full type safety:

// ๐Ÿ›๏ธ Define our product and cart types
interface Product {
  id: string;
  name: string;
  price: number;
  emoji: string;
  stock: number;
}

interface CartItem extends Product {
  quantity: number;
}

interface CartState {
  items: CartItem[];
  total: number;
  isLoading: boolean;
  discountCode?: string;
}

// ๐Ÿช Shopping cart store module
const cartModule = {
  namespaced: true,
  
  state: (): CartState => ({
    items: [],
    total: 0,
    isLoading: false,
    discountCode: undefined
  }),
  
  getters: {
    // ๐Ÿ“Š Calculate total with type safety
    cartTotal: (state: CartState): number => {
      return state.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
    },
    
    // ๐Ÿ”ข Get item count
    itemCount: (state: CartState): number => {
      return state.items.reduce((count, item) => count + item.quantity, 0);
    },
    
    // ๐ŸŽฏ Check if product is in cart
    isInCart: (state: CartState) => (productId: string): boolean => {
      return state.items.some(item => item.id === productId);
    }
  },
  
  mutations: {
    // โž• Add item to cart
    ADD_ITEM(state: CartState, product: Product): void {
      const existingItem = state.items.find(item => item.id === product.id);
      
      if (existingItem) {
        existingItem.quantity++;
        console.log(`๐Ÿ”„ Updated ${product.emoji} ${product.name} quantity`);
      } else {
        state.items.push({ ...product, quantity: 1 });
        console.log(`โž• Added ${product.emoji} ${product.name} to cart!`);
      }
    },
    
    // โž– Remove item from cart
    REMOVE_ITEM(state: CartState, productId: string): void {
      const index = state.items.findIndex(item => item.id === productId);
      if (index > -1) {
        const item = state.items[index];
        console.log(`๐Ÿ—‘๏ธ Removed ${item.emoji} ${item.name} from cart`);
        state.items.splice(index, 1);
      }
    },
    
    // ๐Ÿ”„ Update quantity
    UPDATE_QUANTITY(state: CartState, { productId, quantity }: { productId: string; quantity: number }): void {
      const item = state.items.find(item => item.id === productId);
      if (item) {
        item.quantity = Math.max(0, quantity);
        if (item.quantity === 0) {
          // Auto-remove items with 0 quantity
          const index = state.items.findIndex(i => i.id === productId);
          state.items.splice(index, 1);
        }
      }
    },
    
    // ๐Ÿงน Clear entire cart
    CLEAR_CART(state: CartState): void {
      state.items = [];
      console.log('๐Ÿงน Cart cleared!');
    },
    
    SET_LOADING(state: CartState, loading: boolean): void {
      state.isLoading = loading;
    }
  },
  
  actions: {
    // ๐Ÿ›’ Add product with stock validation
    async addProduct({ commit, state }: any, product: Product): Promise<void> {
      const existingItem = state.items.find((item: CartItem) => item.id === product.id);
      const currentQuantity = existingItem ? existingItem.quantity : 0;
      
      if (currentQuantity < product.stock) {
        commit('ADD_ITEM', product);
      } else {
        console.warn(`โš ๏ธ Cannot add more ${product.name} - out of stock!`);
      }
    },
    
    // ๐Ÿ’ณ Simulate checkout process
    async checkout({ commit, getters, state }: any): Promise<boolean> {
      if (state.items.length === 0) {
        console.warn('โš ๏ธ Cannot checkout empty cart!');
        return false;
      }
      
      commit('SET_LOADING', true);
      
      try {
        // ๐ŸŒ Simulate API call
        await new Promise(resolve => setTimeout(resolve, 2000));
        
        console.log(`๐ŸŽ‰ Order successful! Total: $${getters.cartTotal.toFixed(2)}`);
        commit('CLEAR_CART');
        commit('SET_LOADING', false);
        return true;
      } catch (error) {
        console.error('๐Ÿ’ฅ Checkout failed:', error);
        commit('SET_LOADING', false);
        return false;
      }
    }
  }
};

// ๐ŸŽฎ Usage example
const store = createStore({
  modules: {
    cart: cartModule
  }
});

// ๐Ÿ›๏ธ Sample products
const products: Product[] = [
  { id: '1', name: 'TypeScript Book', price: 29.99, emoji: '๐Ÿ“˜', stock: 5 },
  { id: '2', name: 'Coffee Mug', price: 12.99, emoji: 'โ˜•', stock: 10 },
  { id: '3', name: 'Keyboard', price: 89.99, emoji: 'โŒจ๏ธ', stock: 3 }
];

๐ŸŽฏ Try it yourself: Add a favorites system and implement discount code functionality!

๐ŸŽฎ Example 2: User Authentication State

Letโ€™s create a complete auth system:

// ๐Ÿ‘ค User and auth types
interface User {
  id: string;
  email: string;
  name: string;
  avatar?: string;
  role: 'admin' | 'user' | 'moderator';
}

interface AuthState {
  user: User | null;
  token: string | null;
  isAuthenticated: boolean;
  isLoading: boolean;
  loginAttempts: number;
  lastLoginAt?: Date;
}

// ๐Ÿ” Authentication store module
const authModule = {
  namespaced: true,
  
  state: (): AuthState => ({
    user: null,
    token: localStorage.getItem('auth_token'),
    isAuthenticated: false,
    isLoading: false,
    loginAttempts: 0,
    lastLoginAt: undefined
  }),
  
  getters: {
    // ๐ŸŽฏ Check if user has specific role
    hasRole: (state: AuthState) => (role: User['role']): boolean => {
      return state.user?.role === role || false;
    },
    
    // ๐Ÿ‘ค Get user display name
    displayName: (state: AuthState): string => {
      return state.user?.name || 'Guest User ๐Ÿ‘‹';
    },
    
    // ๐Ÿ”’ Check if user can perform action
    canPerformAction: (state: AuthState) => (requiredRole: User['role']): boolean => {
      if (!state.user) return false;
      
      const roleHierarchy = { 'user': 1, 'moderator': 2, 'admin': 3 };
      const userLevel = roleHierarchy[state.user.role];
      const requiredLevel = roleHierarchy[requiredRole];
      
      return userLevel >= requiredLevel;
    }
  },
  
  mutations: {
    // ๐Ÿš€ Set authenticated user
    SET_USER(state: AuthState, user: User): void {
      state.user = user;
      state.isAuthenticated = true;
      state.lastLoginAt = new Date();
      console.log(`๐Ÿ‘‹ Welcome back, ${user.name}!`);
    },
    
    // ๐Ÿ”‘ Set auth token
    SET_TOKEN(state: AuthState, token: string): void {
      state.token = token;
      localStorage.setItem('auth_token', token);
    },
    
    // ๐Ÿ‘‹ Logout user
    LOGOUT(state: AuthState): void {
      state.user = null;
      state.token = null;
      state.isAuthenticated = false;
      state.loginAttempts = 0;
      localStorage.removeItem('auth_token');
      console.log('๐Ÿ‘‹ See you later!');
    },
    
    SET_LOADING(state: AuthState, loading: boolean): void {
      state.isLoading = loading;
    },
    
    // ๐Ÿ”„ Track login attempts
    INCREMENT_LOGIN_ATTEMPTS(state: AuthState): void {
      state.loginAttempts++;
    },
    
    RESET_LOGIN_ATTEMPTS(state: AuthState): void {
      state.loginAttempts = 0;
    }
  },
  
  actions: {
    // ๐Ÿ” Login action
    async login({ commit, state }: any, credentials: { email: string; password: string }): Promise<boolean> {
      if (state.loginAttempts >= 3) {
        console.warn('โš ๏ธ Too many login attempts. Please try again later.');
        return false;
      }
      
      commit('SET_LOADING', true);
      
      try {
        // ๐ŸŒ Simulate API login
        await new Promise(resolve => setTimeout(resolve, 1500));
        
        // Mock successful login
        const mockUser: User = {
          id: '123',
          email: credentials.email,
          name: 'Sarah Developer',
          avatar: '๐Ÿ‘ฉโ€๐Ÿ’ป',
          role: 'user'
        };
        
        const mockToken = 'jwt_token_here_' + Date.now();
        
        commit('SET_USER', mockUser);
        commit('SET_TOKEN', mockToken);
        commit('RESET_LOGIN_ATTEMPTS');
        commit('SET_LOADING', false);
        
        return true;
      } catch (error) {
        commit('INCREMENT_LOGIN_ATTEMPTS');
        commit('SET_LOADING', false);
        console.error('๐Ÿ’ฅ Login failed:', error);
        return false;
      }
    },
    
    // ๐Ÿ”„ Refresh user session
    async refreshSession({ commit, state }: any): Promise<void> {
      if (!state.token) return;
      
      try {
        // ๐ŸŒ Simulate token refresh
        await new Promise(resolve => setTimeout(resolve, 1000));
        console.log('๐Ÿ”„ Session refreshed successfully!');
      } catch (error) {
        console.error('๐Ÿ’ฅ Session refresh failed:', error);
        commit('LOGOUT');
      }
    },
    
    // ๐Ÿ‘‹ Logout action
    async logout({ commit }: any): Promise<void> {
      commit('SET_LOADING', true);
      
      // ๐ŸŒ Simulate logout API call
      await new Promise(resolve => setTimeout(resolve, 500));
      
      commit('LOGOUT');
      commit('SET_LOADING', false);
    }
  }
};

๐Ÿš€ Advanced Concepts

๐Ÿง™โ€โ™‚๏ธ Advanced Topic 1: Typed Store Modules

When youโ€™re ready to level up, try creating fully typed store modules:

// ๐ŸŽฏ Advanced typed module system
import { Module, ActionContext } from 'vuex';

// ๐Ÿ—๏ธ Define module state types
interface NotificationState {
  notifications: Notification[];
  maxNotifications: number;
}

interface Notification {
  id: string;
  type: 'success' | 'error' | 'warning' | 'info';
  title: string;
  message: string;
  emoji: string;
  timestamp: Date;
  autoClose: boolean;
  duration: number;
}

// ๐ŸŽจ Root state interface
interface RootState {
  notifications: NotificationState;
  auth: AuthState;
  // ... other modules
}

// ๐Ÿ”ง Typed action context
type NotificationActionContext = ActionContext<NotificationState, RootState>;

// ๐Ÿช Fully typed notification module
const notificationModule: Module<NotificationState, RootState> = {
  namespaced: true,
  
  state: (): NotificationState => ({
    notifications: [],
    maxNotifications: 5
  }),
  
  getters: {
    activeNotifications: (state): Notification[] => {
      return state.notifications.filter(n => !n.autoClose || 
        Date.now() - n.timestamp.getTime() < n.duration);
    },
    
    hasErrors: (state): boolean => {
      return state.notifications.some(n => n.type === 'error');
    }
  },
  
  mutations: {
    ADD_NOTIFICATION(state: NotificationState, notification: Omit<Notification, 'id' | 'timestamp'>): void {
      const newNotification: Notification = {
        ...notification,
        id: Date.now().toString(),
        timestamp: new Date()
      };
      
      state.notifications.unshift(newNotification);
      
      // Keep only max notifications
      if (state.notifications.length > state.maxNotifications) {
        state.notifications = state.notifications.slice(0, state.maxNotifications);
      }
      
      console.log(`${notification.emoji} ${notification.title}`);
    },
    
    REMOVE_NOTIFICATION(state: NotificationState, notificationId: string): void {
      const index = state.notifications.findIndex(n => n.id === notificationId);
      if (index > -1) {
        state.notifications.splice(index, 1);
      }
    }
  },
  
  actions: {
    showSuccess({ commit }: NotificationActionContext, { title, message }: { title: string; message: string }): void {
      commit('ADD_NOTIFICATION', {
        type: 'success',
        title,
        message,
        emoji: 'โœ…',
        autoClose: true,
        duration: 3000
      });
    },
    
    showError({ commit }: NotificationActionContext, { title, message }: { title: string; message: string }): void {
      commit('ADD_NOTIFICATION', {
        type: 'error',
        title,
        message,
        emoji: 'โŒ',
        autoClose: false,
        duration: 0
      });
    }
  }
};

๐Ÿ—๏ธ Advanced Topic 2: Store Composition with TypeScript

For complex applications, compose multiple typed modules:

// ๐Ÿš€ Advanced store composition
import { createStore, Store } from 'vuex';
import { InjectionKey } from 'vue';

// ๐ŸŽฏ Create injection key for typed store
export const key: InjectionKey<Store<RootState>> = Symbol();

// ๐Ÿช Composed store with all modules
export const store = createStore<RootState>({
  modules: {
    auth: authModule,
    cart: cartModule,
    notifications: notificationModule
  },
  
  // ๐Ÿ”ง Global plugins
  plugins: [
    // ๐Ÿ’พ Persistence plugin
    (store) => {
      store.subscribe((mutation, state) => {
        if (mutation.type.startsWith('cart/')) {
          localStorage.setItem('cart_state', JSON.stringify(state.cart));
        }
      });
    }
  ]
});

// ๐ŸŽจ Helper for typed store access in components
export function useTypedStore(): Store<RootState> {
  return useStore(key);
}

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Losing Type Safety in Actions

// โŒ Wrong way - losing type information!
const badAction = async ({ commit }: any, payload: any): Promise<void> => {
  commit('SOME_MUTATION', payload); // ๐Ÿ’ฅ No type checking!
};

// โœ… Correct way - maintain full type safety!
const goodAction = async (
  { commit }: ActionContext<CartState, RootState>, 
  product: Product
): Promise<void> => {
  commit('ADD_ITEM', product); // โœ… Fully typed!
};

๐Ÿคฏ Pitfall 2: Forgetting Namespaced Module Types

// โŒ Dangerous - accessing non-namespaced!
store.commit('ADD_ITEM', product); // ๐Ÿ’ฅ Won't work with namespaced modules!

// โœ… Safe - use correct namespace!
store.commit('cart/ADD_ITEM', product); // โœ… Correctly namespaced!

๐Ÿ˜ฐ Pitfall 3: Mutation vs Action Confusion

// โŒ Wrong - async operation in mutation
mutations: {
  async FETCH_DATA(state): Promise<void> { // ๐Ÿ’ฅ Mutations must be synchronous!
    const data = await api.getData();
    state.data = data;
  }
}

// โœ… Correct - async in actions, sync in mutations
actions: {
  async fetchData({ commit }: ActionContext<State, RootState>): Promise<void> {
    const data = await api.getData(); // โœ… Async operations belong here
    commit('SET_DATA', data); // โœ… Then commit synchronously
  }
},
mutations: {
  SET_DATA(state: State, data: any[]): void { // โœ… Synchronous only
    state.data = data;
  }
}

๐Ÿ› ๏ธ Best Practices

  1. ๐ŸŽฏ Always Type Your State: Define interfaces for all state structures
  2. ๐Ÿ“ Use Namespaced Modules: Keep your store organized and prevent conflicts
  3. ๐Ÿ›ก๏ธ Strict Typing: Use proper ActionContext types for actions
  4. โœจ Keep Mutations Simple: Only synchronous state changes in mutations
  5. ๐Ÿ”„ Actions for Async: All async operations belong in actions
  6. ๐Ÿ’พ Consider Persistence: Save important state to localStorage
  7. ๐Ÿงช Test Your Store: Write unit tests for your state management logic

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Task Management Store

Create a complete task management system with full TypeScript support:

๐Ÿ“‹ Requirements:

  • โœ… Tasks with title, description, status, priority, and due dates
  • ๐Ÿท๏ธ Categories for task organization (work, personal, urgent)
  • ๐Ÿ‘ค User assignment and collaboration features
  • ๐Ÿ“Š Analytics and progress tracking
  • ๐Ÿ” Advanced filtering and sorting
  • ๐ŸŽจ Each task needs an emoji and color coding!

๐Ÿš€ Bonus Points:

  • Add drag-and-drop reordering
  • Implement task dependencies
  • Create time tracking functionality
  • Add team collaboration features
  • Build a dashboard with charts

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
// ๐ŸŽฏ Complete task management system with TypeScript!

// ๐Ÿ“‹ Task-related types
interface Task {
  id: string;
  title: string;
  description: string;
  status: 'todo' | 'in_progress' | 'completed' | 'blocked';
  priority: 'low' | 'medium' | 'high' | 'urgent';
  category: 'work' | 'personal' | 'urgent' | 'health' | 'learning';
  emoji: string;
  color: string;
  dueDate?: Date;
  createdAt: Date;
  updatedAt: Date;
  assignee?: string;
  timeSpent: number; // in minutes
  estimatedTime?: number; // in minutes
  tags: string[];
  dependencies: string[]; // task IDs this task depends on
}

interface TaskState {
  tasks: Task[];
  categories: string[];
  selectedCategory: string | null;
  sortBy: 'dueDate' | 'priority' | 'createdAt' | 'title';
  sortOrder: 'asc' | 'desc';
  isLoading: boolean;
  searchQuery: string;
}

// ๐Ÿช Task management store module
const taskModule: Module<TaskState, RootState> = {
  namespaced: true,
  
  state: (): TaskState => ({
    tasks: [],
    categories: ['work', 'personal', 'urgent', 'health', 'learning'],
    selectedCategory: null,
    sortBy: 'dueDate',
    sortOrder: 'asc',
    isLoading: false,
    searchQuery: ''
  }),
  
  getters: {
    // ๐Ÿ“Š Get tasks by status
    tasksByStatus: (state: TaskState) => (status: Task['status']): Task[] => {
      return state.tasks.filter(task => task.status === status);
    },
    
    // ๐ŸŽฏ Get filtered and sorted tasks
    filteredTasks: (state: TaskState): Task[] => {
      let filtered = state.tasks;
      
      // Filter by category
      if (state.selectedCategory) {
        filtered = filtered.filter(task => task.category === state.selectedCategory);
      }
      
      // Filter by search query
      if (state.searchQuery) {
        const query = state.searchQuery.toLowerCase();
        filtered = filtered.filter(task => 
          task.title.toLowerCase().includes(query) ||
          task.description.toLowerCase().includes(query) ||
          task.tags.some(tag => tag.toLowerCase().includes(query))
        );
      }
      
      // Sort tasks
      filtered.sort((a, b) => {
        let compareValue = 0;
        
        switch (state.sortBy) {
          case 'priority':
            const priorityOrder = { 'low': 1, 'medium': 2, 'high': 3, 'urgent': 4 };
            compareValue = priorityOrder[a.priority] - priorityOrder[b.priority];
            break;
          case 'dueDate':
            if (a.dueDate && b.dueDate) {
              compareValue = a.dueDate.getTime() - b.dueDate.getTime();
            } else if (a.dueDate) {
              compareValue = -1;
            } else if (b.dueDate) {
              compareValue = 1;
            }
            break;
          case 'createdAt':
            compareValue = a.createdAt.getTime() - b.createdAt.getTime();
            break;
          case 'title':
            compareValue = a.title.localeCompare(b.title);
            break;
        }
        
        return state.sortOrder === 'asc' ? compareValue : -compareValue;
      });
      
      return filtered;
    },
    
    // ๐Ÿ“ˆ Get completion statistics
    completionStats: (state: TaskState) => {
      const total = state.tasks.length;
      const completed = state.tasks.filter(t => t.status === 'completed').length;
      const inProgress = state.tasks.filter(t => t.status === 'in_progress').length;
      const blocked = state.tasks.filter(t => t.status === 'blocked').length;
      
      return {
        total,
        completed,
        inProgress,
        blocked,
        completionRate: total > 0 ? Math.round((completed / total) * 100) : 0
      };
    },
    
    // โฐ Get overdue tasks
    overdueTasks: (state: TaskState): Task[] => {
      const now = new Date();
      return state.tasks.filter(task => 
        task.dueDate && 
        task.dueDate < now && 
        task.status !== 'completed'
      );
    },
    
    // ๐ŸŽฏ Get task dependencies
    getTaskDependencies: (state: TaskState) => (taskId: string): Task[] => {
      const task = state.tasks.find(t => t.id === taskId);
      if (!task) return [];
      
      return task.dependencies
        .map(depId => state.tasks.find(t => t.id === depId))
        .filter(Boolean) as Task[];
    }
  },
  
  mutations: {
    // โž• Add new task
    ADD_TASK(state: TaskState, taskData: Omit<Task, 'id' | 'createdAt' | 'updatedAt'>): void {
      const newTask: Task = {
        ...taskData,
        id: Date.now().toString() + Math.random().toString(36).substr(2, 9),
        createdAt: new Date(),
        updatedAt: new Date()
      };
      
      state.tasks.unshift(newTask);
      console.log(`โœ… Created task: ${newTask.emoji} ${newTask.title}`);
    },
    
    // ๐Ÿ“ Update existing task
    UPDATE_TASK(state: TaskState, { taskId, updates }: { taskId: string; updates: Partial<Task> }): void {
      const taskIndex = state.tasks.findIndex(t => t.id === taskId);
      if (taskIndex > -1) {
        state.tasks[taskIndex] = {
          ...state.tasks[taskIndex],
          ...updates,
          updatedAt: new Date()
        };
        console.log(`๐Ÿ”„ Updated task: ${state.tasks[taskIndex].title}`);
      }
    },
    
    // ๐Ÿ—‘๏ธ Delete task
    DELETE_TASK(state: TaskState, taskId: string): void {
      const taskIndex = state.tasks.findIndex(t => t.id === taskId);
      if (taskIndex > -1) {
        const task = state.tasks[taskIndex];
        state.tasks.splice(taskIndex, 1);
        console.log(`๐Ÿ—‘๏ธ Deleted task: ${task.title}`);
      }
    },
    
    // ๐ŸŽฏ Set category filter
    SET_CATEGORY_FILTER(state: TaskState, category: string | null): void {
      state.selectedCategory = category;
    },
    
    // ๐Ÿ” Set search query
    SET_SEARCH_QUERY(state: TaskState, query: string): void {
      state.searchQuery = query;
    },
    
    // ๐Ÿ“Š Set sorting
    SET_SORTING(state: TaskState, { sortBy, sortOrder }: { sortBy: TaskState['sortBy']; sortOrder: TaskState['sortOrder'] }): void {
      state.sortBy = sortBy;
      state.sortOrder = sortOrder;
    },
    
    SET_LOADING(state: TaskState, loading: boolean): void {
      state.isLoading = loading;
    }
  },
  
  actions: {
    // ๐Ÿš€ Create task with validation
    async createTask({ commit }: ActionContext<TaskState, RootState>, taskData: Omit<Task, 'id' | 'createdAt' | 'updatedAt'>): Promise<boolean> {
      try {
        // ๐Ÿ” Validate task data
        if (!taskData.title.trim()) {
          console.warn('โš ๏ธ Task title is required');
          return false;
        }
        
        if (taskData.dueDate && taskData.dueDate < new Date()) {
          console.warn('โš ๏ธ Due date cannot be in the past');
          return false;
        }
        
        commit('ADD_TASK', taskData);
        return true;
      } catch (error) {
        console.error('๐Ÿ’ฅ Failed to create task:', error);
        return false;
      }
    },
    
    // โœ… Complete task
    async completeTask({ commit, getters }: ActionContext<TaskState, RootState>, taskId: string): Promise<void> {
      const task = getters.filteredTasks.find((t: Task) => t.id === taskId);
      if (task) {
        commit('UPDATE_TASK', {
          taskId,
          updates: { status: 'completed' as const }
        });
        
        // ๐ŸŽ‰ Show success notification
        await this.dispatch('notifications/showSuccess', {
          title: 'Task Completed! ๐ŸŽ‰',
          message: `Great job finishing "${task.title}"!`
        }, { root: true });
      }
    },
    
    // ๐Ÿ“Š Generate productivity report
    async generateReport({ state, getters }: ActionContext<TaskState, RootState>): Promise<any> {
      const stats = getters.completionStats;
      const overdueTasks = getters.overdueTasks;
      
      const report = {
        summary: stats,
        overdueTasks: overdueTasks.length,
        categoryBreakdown: state.categories.map(category => ({
          category,
          total: state.tasks.filter(t => t.category === category).length,
          completed: state.tasks.filter(t => t.category === category && t.status === 'completed').length
        })),
        timeTracking: {
          totalTimeSpent: state.tasks.reduce((sum, task) => sum + task.timeSpent, 0),
          averageTimePerTask: state.tasks.length > 0 
            ? Math.round(state.tasks.reduce((sum, task) => sum + task.timeSpent, 0) / state.tasks.length)
            : 0
        }
      };
      
      console.log('๐Ÿ“Š Productivity Report Generated:', report);
      return report;
    }
  }
};

// ๐ŸŽฎ Usage example in a component
export default {
  setup() {
    const store = useTypedStore();
    
    // ๐Ÿ“Š Reactive computed properties
    const tasks = computed(() => store.getters['tasks/filteredTasks']);
    const stats = computed(() => store.getters['tasks/completionStats']);
    const overdueTasks = computed(() => store.getters['tasks/overdueTasks']);
    
    // ๐ŸŽฏ Task management methods
    const createTask = async (taskData: Omit<Task, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
      await store.dispatch('tasks/createTask', taskData);
    };
    
    const completeTask = async (taskId: string): Promise<void> => {
      await store.dispatch('tasks/completeTask', taskId);
    };
    
    const generateReport = async (): Promise<void> => {
      await store.dispatch('tasks/generateReport');
    };
    
    return {
      tasks,
      stats,
      overdueTasks,
      createTask,
      completeTask,
      generateReport
    };
  }
};

๐ŸŽ“ Key Takeaways

Youโ€™ve learned so much! Hereโ€™s what you can now do:

  • โœ… Create type-safe Vuex stores with confidence ๐Ÿ’ช
  • โœ… Avoid common state management mistakes that trip up beginners ๐Ÿ›ก๏ธ
  • โœ… Apply TypeScript best practices in Vue applications ๐ŸŽฏ
  • โœ… Debug complex state issues like a pro ๐Ÿ›
  • โœ… Build scalable applications with predictable state! ๐Ÿš€

Remember: Vuex with TypeScript is your superpower for building reliable, maintainable Vue applications. The initial setup takes time, but it pays dividends in the long run! ๐Ÿค

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve mastered Vuex with TypeScript state management!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Practice with the task management exercise above
  2. ๐Ÿ—๏ธ Build a real project using typed Vuex stores
  3. ๐Ÿ“š Move on to our next tutorial: Pinia with TypeScript (Modern State Management)
  4. ๐ŸŒŸ Share your awesome projects with the community!

Remember: Every Vue expert was once a beginner. Keep building, keep learning, and most importantly, have fun with your type-safe state management! ๐Ÿš€


Happy coding! ๐ŸŽ‰๐Ÿš€โœจ