use-chat

repository·main·Indexed 41 days ago

https://github.com/chatscope/use-chat

A headless React hook library for managing complex chat application state, including message grouping, conversation lists, user presence, and typing indicators. Version 3.1.2 provides a state management architecture consisting of Storage, ChatProvider, and the useChat hook, requiring an IChatService implementation to connect to a backend.

Tokens
10.5K
Snippets
30
Records
50
Agent score
50%

What's inside use-chat

  1. How the architecture of use-chat works

    main

    The library is a headless state management tool for chat applications, composed of three main parts:

    1. Storage: The source of truth where state (conversations, messages, users, etc.) is kept. You can use the provided BasicStorage or implement your own IStorage (e.g., using Redux).
    2. ChatProvider: A React context provider that connects the storage and the service factory to your application.
    3. useChat: The primary hook used in your components to access chat state and methods.

    To connect to a real backend, you must implement the IChatService interface. This service handles the communication protocol (sending/receiving messages, typing indicators, etc.).

  2. Implement a Service Factory

    main

    To initialize the ChatProvider, you must provide a serviceFactory function. This function is responsible for instantiating your IChatService implementation.

    It receives two arguments:

    1. storage: An instance of IChatStorage (allowing the service to read/write state).
    2. updateState: A function that must be called whenever the service performs a write operation to the storage to trigger React re-renders.

    Example implementation pattern:

    const serviceFactory = (storage, updateState) => {
      return new MyCustomChatService(storage, updateState);
    };
  3. Configure ChatProvider

    main

    Wrap your application (or chat component tree) with ChatProvider. You must pass the serviceFactory, a storage instance, and an optional config object.

    Config Options:

    • typingThrottleTime: Time in ms to throttle received typing indicators.
    • typingDebounceTime: Time in ms to debounce sent typing indicators.
    • debounceTyping: Boolean to enable/disable debouncing of sent typing indicators.
    • autoDraft: Configuration for message drafts (e.g., AutoDraft.Save | AutoDraft.Restore).
    import {
      BasicStorage,
      ChatProvider,
      AutoDraft
    } from "@chatscope/use-chat";
    
    const chatStorage = new BasicStorage({
      groupIdGenerator: () => "some-id",
      messageIdGenerator: () => "some-id"
    });
    
    const serviceFactory = (storage, updateState) => {
      return new MyChatService(storage, updateState);
    };
    
    <ChatProvider 
      serviceFactory={serviceFactory} 
      storage={chatStorage} 
      config={{
        typingThrottleTime: 250,
        typingDebounceTime: 900,
        debounceTyping: true,
        autoDraft: AutoDraft.Save | AutoDraft.Restore
      }}
    >
      <App />
    </ChatProvider>
  4. Use the useChat hook to manage chat state

    main

    The useChat hook provides access to the current state of the chat and methods to interact with it. Common properties and methods include:

    • currentMessages: An array of grouped messages for the active conversation.
    • conversations: A list of all available conversations.
    • activeConversation: The currently selected conversation object.
    • setActiveConversation(id): Method to switch the active chat.
    • sendMessage({ message, conversationId, senderId }): Method to send a message.
    • getUser(id): Method to retrieve user data by ID.

    Example usage:

    import { useChat, ChatMessage, MessageContentType, MessageDirection, MessageStatus } from "@chatscope/use-chat";
    
    export const Chat = () => {
      const { 
        currentMessages, 
        conversations, 
        activeConversation, 
        setActiveConversation, 
        sendMessage, 
        getUser 
      } = useChat();
    
      const handleSend = text => {
        const message = new ChatMessage({
          id: "",
          content: text,
          contentType: MessageContentType.TextHtml,
          senderId: "my-user-id",
          direction: MessageDirection.Outgoing,
          status: MessageStatus.Sent
        });
    
        sendMessage({
          message,
          conversationId: activeConversation.id,
          senderId: "my-user-id",
        });
      };
    
      // ... render logic
    };
  5. Manage conversations in BasicStorage

    main

    Use the following methods to manage conversations within BasicStorage:

    • addConversation(conversation: Conversation<ConversationData>): boolean: Adds a new conversation. Returns true if added.
    • getConversation(conversationId: ConversationId): [Conversation<ConversationData>, number] | [undefined, undefined]: Retrieves a conversation and its index.
    • updateConversation(conversation: Conversation<ConversationData>): void: Replaces an existing conversation with updated data.
    • removeConversation(conversationId: ConversationId, removeMessages?: boolean): boolean: Removes a conversation. If removeMessages is true (default), all messages associated with this conversation are also deleted.
    • conversationExists(conversationId: ConversationId): boolean: Checks if a conversation ID exists.
    • setUnread(conversationId: ConversationId, count: number): void: Updates the unread message counter for a conversation.
    • setActiveConversation(conversationId?: ConversationId, resetUnreadCounter?: boolean): void: Sets the active conversation. If resetUnreadCounter is true (default), the unread counter for the new active conversation is set to 0.
  6. Use Chat core types and interfaces

    main
    The @chatscope/use-chat package exports a comprehensive set of types and interfaces required to build chat applications. Key entities include ChatMessage, Conversation, Participant, User, Presence, TypingUser, and TypingUsersList. These types define the data contracts for messages, conversation structures, and user states within the library.
  7. Manage users in BasicStorage

    main

    Use the following methods to manage the user collection within BasicStorage:

    • setCurrentUser(user: User): Sets the currently logged-in user.
    • addUser(user: User): boolean: Adds a user to the collection. Returns true if the user was added (i.e., they didn't already exist).
    • removeUser(userId: UserId): boolean: Removes a user by ID. Returns true if the user was found and removed.
    • getUser(userId: UserId): [User, number] | [undefined, undefined]: Retrieves a user and their index in the collection.
    • userExists(userId: UserId): boolean: Checks if a user ID is present in the collection.
    • setPresence(userId: UserId, presence: Presence): void: Updates the presence status (e.g., online, offline) for a specific user.
  8. Manage groups of messages with MessageGroup

    main

    The MessageGroup class is used to organize a collection of ChatMessage objects associated with a specific sender and direction. It provides methods to add, retrieve, update, and replace messages within that group.

    Constructor Parameters

    When instantiating MessageGroup, provide an object with:

    • id: A unique string identifier for the group.
    • senderId: The UserId of the sender.
    • direction: The MessageDirection (e.g., incoming or outgoing).
    import { MessageGroup } from './MessageGroup';
    import { MessageDirection } from './enums';
    
    const group = new MessageGroup({
      id: 'group-123',
      senderId: 'user-456',
      direction: MessageDirection.Incoming
    });
  9. Manage typing users with TypingUsersList

    main

    The TypingUsersList class is used to maintain a collection of users who are currently typing. It provides methods to add, remove, and locate users within the list based on their UserId.

    Key Methods

    • addUser(typingUser: TypingUser): Adds a user to the list. If a user with the same userId already exists, the existing entry is replaced with the new typingUser object.
    • removeUser(userId: UserId): Removes the user with the specified ID from the collection.
    • findIndex(userId: UserId): Returns the index of the user in the list, or -1 if the user is not found.
    • length: A getter that returns the current number of typing users in the list.
    import { TypingUsersList } from './TypingUsersList';
    import { TypingUser } from './TypingUser';
    
    // Initialize the list
    const typingList = new TypingUsersList({ items: [] });
    
    // Add a user
    typingList.addUser({ userId: 'user-123', name: 'John Doe' } as TypingUser);
    
    // Check length
    console.log(typingList.length); // 1
    
    // Remove a user
    typingList.removeUser('user-123');
    
    // Find index
    const index = typingList.findIndex('user-123'); // -1
  10. Define message content types

    main

    The MessageContent interface and its specialized implementations define the structure of data carried by a message. Depending on the MessageContentType, the content field (or specific fields like url and data) will hold different data types such as strings, arrays of items, or binary data.

    Common content types include:

    • Text-based: TextContent (plain text), MarkdownContent (markdown), HtmlContent (HTML), and KmlContent (KML strings).
    • Media: ImageContent, VideoContent, and GalleryContent (an array of GalleryItem).
    • Files: AttachmentContent and AttachmentListContent.
    • Other: VCardContent and OtherContent (using unknown for flexible payloads).

    Note on Binary vs URL: For ImageContent, AttachmentContent, and VideoContent, if the url is an empty string, the content is treated as binary data provided in the data field (ArrayBuffer).

    // Example of an ImageContent object
    const imageMessage: ImageContent = {
      url: "https://example.com/image.png",
      data: new ArrayBuffer(0) // or actual buffer if url is empty
    };
    
    // Example of a GalleryContent object
    const galleryMessage: GalleryContent = {
      content: [
        { description: "First image", src: "https://example.com/1.png" },
        { description: "Second image", src: "https://example.com/2.png" }
      ]
    };