Overview

The Chat Store is the core of the messaging functionality in Bimble Chat Vue. Built using Pinia's Composition API, it manages chat conversations, message history, search functionality, and conversation states with full TypeScript support.

Key Features

Reactive chat list management, real-time search, conversation state tracking, and seamless integration with the mock API system.

Store Structure

The Chat Store uses the Composition API pattern and is located at src/stores/modules/chat.ts:

import { defineStore } from 'pinia';
import { ref } from 'vue';
import axios from '@/utils/axios';
import type { Chat } from '@/types/chat';

export const useChatStore = defineStore('chat', () => {
  // State
  const chats = ref<Chat[]>([]);
  const loading = ref(false);
  const error = ref<string | null>(null);
  const isConversationActive = ref(false);

  // Actions
  const setConversationActive = (active: boolean) => {
    isConversationActive.value = active;
  };

  const fetchChats = async () => {
    // Fetch logic...
  };

  const searchChats = async (query: string) => {
    // Search logic...
  };

  return {
    chats,
    loading,
    error,
    isConversationActive,
    setConversationActive,
    fetchChats,
    searchChats
  };
});

State Properties

The Chat Store maintains the following reactive state properties:

chats

Array of chat conversations with full message history and metadata

Chat[]
loading

Boolean flag indicating if any async operation is in progress

boolean
error

Error message from failed operations, null when no error

string | null
isConversationActive

Tracks whether a conversation view is currently active

boolean

Type Definitions

The Chat Store uses strong TypeScript typing with interfaces defined in src/types/chat.ts:

export interface Chat {
  id: number;
  name: string;
  avatar: string;
  lastMessage: string;
  timestamp: Date;
  unreadCount: number;
  status: 'online' | 'offline' | 'away' | 'busy';
  isPinned: boolean;
  type: 'single' | 'group' | 'archived';
  email?: string;
  username?: string;
  lastSeen?: Date;
  chatHistory?: ChatMessage[];
}
export interface ChatMessage {
  id: string;
  senderId: number;
  msg: string;
  createdAt: Date;
  type: string;
  attachment: any[];
  replyTo?: {
    id: string;
    msg: string;
    senderName: string;
  };
}

export interface Message {
  id: number;
  chatId: number;
  senderId: number;
  content: string;
  timestamp: string;
  messageType: 'text' | 'image' | 'file' | 'voice';
  isRead: boolean;
}

Actions

The Chat Store provides several actions for managing chat functionality:

fetchChats()

Fetches all chat conversations from the API and transforms the data

const fetchChats = async () => {
  loading.value = true;
  error.value = null;
  
  try {
    const response = await axios.get('/api/data/chat/ChatData');
    chats.value = response.data.map((chatData: any) => ({
      id: chatData.id,
      name: chatData.name,
      avatar: chatData.thumb,
      lastMessage: chatData.chatHistory[chatData.chatHistory.length - 1]?.msg || '',
      timestamp: chatData.chatHistory[chatData.chatHistory.length - 1]?.createdAt || new Date(),
      unreadCount: Math.floor(Math.random() * 5),
      status: chatData.status,
      isPinned: chatData.recent,
      type: chatData.type,
      email: chatData.email,
      username: chatData.username,
      lastSeen: chatData.lastSeen,
      chatHistory: chatData.chatHistory
    }));
  } catch (err) {
    error.value = 'Failed to fetch chats';
  } finally {
    loading.value = false;
  }
};
searchChats(query)

Searches through chats by name and message content

const searchChats = async (query: string) => {
  loading.value = true;
  error.value = null;
  
  try {
    const response = await axios.get('/api/data/chat/ChatData');
    const filteredData = response.data.filter((chatData: any) => 
      chatData.name.toLowerCase().includes(query.toLowerCase()) ||
      chatData.chatHistory.some((msg: any) => 
        msg.msg.toLowerCase().includes(query.toLowerCase())
      )
    );
    
    chats.value = filteredData.map((chatData: any) => ({
      // Transform filtered data...
    }));
  } catch (err) {
    error.value = 'Failed to search chats';
  } finally {
    loading.value = false;
  }
};
setConversationActive()

Updates the conversation active state for UI management

const setConversationActive = (active: boolean) => {
  isConversationActive.value = active;
};

Usage: Called when opening/closing conversation views to manage UI state and responsive behavior.

Usage Examples

Here are practical examples of using the Chat Store in Vue components:

1
Basic Setup in Component

Import and initialize the Chat Store in your Vue component:

<script setup lang="ts">
import { onMounted, computed } from 'vue'
import { useChatStore } from '@/stores/modules/chat'

const chatStore = useChatStore()

// Reactive computed properties
const chats = computed(() => chatStore.chats)
const isLoading = computed(() => chatStore.loading)
const hasError = computed(() => chatStore.error)

// Load chats on component mount
onMounted(async () => {
  await chatStore.fetchChats()
})
</script>
2
Chat List Component

Display the chat list with search functionality:

<template>
  <div class="chat-list">
    <!-- Search Input -->
    <v-text-field
      v-model="searchQuery"
      @input="handleSearch"
      placeholder="Search conversations..."
      prepend-inner-icon="mdi-magnify"
    />
    
    <!-- Loading State -->
    <v-progress-circular v-if="isLoading" indeterminate />
    
    <!-- Error State -->
    <v-alert v-if="hasError" type="error">
      {{ hasError }}
    </v-alert>
    
    <!-- Chat List -->
    <v-list v-if="!isLoading && !hasError">
      <v-list-item
        v-for="chat in chats"
        :key="chat.id"
        @click="openChat(chat)"
      >
        <template #prepend>
          <v-avatar>
            <v-img :src="chat.avatar" :alt="chat.name" />
          </v-avatar>
        </template>
        
        <v-list-item-title>{{ chat.name }}</v-list-item-title>
        <v-list-item-subtitle>{{ chat.lastMessage }}</v-list-item-subtitle>
        
        <template #append>
          <div class="chat-meta">
            <span class="timestamp">{{ formatTime(chat.timestamp) }}</span>
            <v-chip v-if="chat.unreadCount > 0" size="small" color="primary">
              {{ chat.unreadCount }}
            </v-chip>
          </div>
        </template>
      </v-list-item>
    </v-list>
  </div>
</template>
3
Search Functionality

Implement real-time search with debouncing:

import { ref, watch } from 'vue'
import { debounce } from 'lodash-es'

const searchQuery = ref('')

// Debounced search function
const debouncedSearch = debounce(async (query: string) => {
  if (query.trim()) {
    await chatStore.searchChats(query)
  } else {
    await chatStore.fetchChats()
  }
}, 300)

// Watch for search query changes
watch(searchQuery, (newQuery) => {
  debouncedSearch(newQuery)
})

const openChat = (chat: Chat) => {
  chatStore.setConversationActive(true)
  // Navigate to chat or emit event
}

Data Transformation

The Chat Store transforms raw API data into the standardized Chat interface:

// Raw API Data Structure
const rawChatData = {
  id: 1,
  name: 'Alice Johnson',
  status: 'online',
  thumb: 'avatar1.jpg',
  recent: true,
  type: 'single',
  email: 'alice@example.com',
  username: '@alice_j',
  lastSeen: new Date(),
  chatHistory: [
    {
      id: 'msg_1_1',
      senderId: 1,
      msg: 'Hello!',
      createdAt: new Date(),
      type: 'text',
      attachment: []
    }
  ]
}

// Transformed Chat Object
const transformedChat = {
  id: chatData.id,
  name: chatData.name,
  avatar: chatData.thumb,
  lastMessage: chatData.chatHistory[chatData.chatHistory.length - 1]?.msg || '',
  timestamp: chatData.chatHistory[chatData.chatHistory.length - 1]?.createdAt || new Date(),
  unreadCount: Math.floor(Math.random() * 5), // Demo purposes
  status: chatData.status,
  isPinned: chatData.recent,
  type: chatData.type,
  email: chatData.email,
  username: chatData.username,
  lastSeen: chatData.lastSeen,
  chatHistory: chatData.chatHistory
}

Error Handling

The Chat Store implements comprehensive error handling for robust user experience:

Error Handling Features
  • Try-catch blocks for all async operations
  • User-friendly error messages
  • Error state reset on new operations
  • Console logging for debugging
  • Loading state management
Error Types
  • Failed to fetch chats: Network or API errors
  • Failed to search chats: Search operation errors
  • Automatic error clearing on successful operations

Best Practices

Do
  • Use computed properties for reactive data access
  • Handle loading and error states in UI
  • Debounce search operations
  • Clear errors before new operations
  • Use TypeScript for type safety
Avoid
  • Directly modifying store state from components
  • Ignoring error states in the UI
  • Making API calls on every keystroke
  • Forgetting to handle loading states
  • Bypassing the store for chat data

Integration with Mock API

The Chat Store seamlessly integrates with the mock API system for development and testing:

// API Endpoint
const response = await axios.get('/api/data/chat/ChatData');

// Mock API Configuration (mockApis/chat/index.ts)
mock.onGet('/api/data/chat/ChatData').reply(() => {
  return [200, ChatData]; // Returns static chat data
});

// Data includes:
// - 12 different chat conversations
// - Various message types (text, images, files, voice)
// - Different user statuses and types
// - Message history with timestamps
// - Attachment support