Skip to main content
Quick Reference for AI Agents & Developers
// Start typing
const typingIndicator = new CometChat.TypingIndicator(
  "receiver_uid",  // or group_guid
  CometChat.RECEIVER_TYPE.USER  // or GROUP
);
CometChat.startTyping(typingIndicator);

// Stop typing
CometChat.endTyping(typingIndicator);

// Listen for typing events
CometChat.addMessageListener("TYPING_LISTENER", new CometChat.MessageListener({
  onTypingStarted: (indicator) => {
    console.log(indicator.getSender().getName(), "is typing...");
  },
  onTypingEnded: (indicator) => {
    console.log(indicator.getSender().getName(), "stopped typing");
  }
}));

// Best practice: Debounce and auto-stop after 2s of inactivity
Typing indicators let users know when someone is composing a message, creating a more engaging chat experience.
Available via: SDK | UI Kits

Send Typing Indicator

Start Typing

Notify the recipient that you’ve started typing:
const receiverID = "user_uid";
const receiverType = CometChat.RECEIVER_TYPE.USER;

const typingNotification = new CometChat.TypingIndicator(
  receiverID,
  receiverType
);

CometChat.startTyping(typingNotification);

Stop Typing

Notify when you’ve stopped typing:
const receiverID = "user_uid";
const receiverType = CometChat.RECEIVER_TYPE.USER;

const typingNotification = new CometChat.TypingIndicator(
  receiverID,
  receiverType
);

CometChat.endTyping(typingNotification);

Receive Typing Indicators

Listen for typing events from other users:
const listenerID = "TYPING_LISTENER";

CometChat.addMessageListener(
  listenerID,
  new CometChat.MessageListener({
    onTypingStarted: (typingIndicator) => {
      console.log("Typing started:", typingIndicator);
      
      const sender = typingIndicator.getSender();
      console.log(sender.getName(), "is typing...");
    },
    onTypingEnded: (typingIndicator) => {
      console.log("Typing ended:", typingIndicator);
      
      const sender = typingIndicator.getSender();
      console.log(sender.getName(), "stopped typing");
    }
  })
);

TypingIndicator Properties

PropertyMethodDescription
SendergetSender()User object of who is typing
Receiver IDgetReceiverId()UID or GUID of recipient
Receiver TypegetReceiverType()user or group
MetadatagetMetadata()Custom data (optional)

Add Custom Metadata

Send additional data with typing indicators:
const typingNotification = new CometChat.TypingIndicator(
  receiverID,
  receiverType
);

typingNotification.setMetadata({
  isVoiceMessage: true,
  customField: "value"
});

CometChat.startTyping(typingNotification);
Access metadata when receiving:
onTypingStarted: (typingIndicator) => {
  const metadata = typingIndicator.getMetadata();
  if (metadata?.isVoiceMessage) {
    console.log("Recording voice message...");
  }
}

Implementation Example

Here’s a complete implementation with debouncing:
class TypingHandler {
  constructor(receiverID, receiverType) {
    this.receiverID = receiverID;
    this.receiverType = receiverType;
    this.typingTimeout = null;
    this.isTyping = false;
  }

  // Call this on every keystroke in the input field
  onInputChange() {
    // Start typing if not already
    if (!this.isTyping) {
      this.startTyping();
    }

    // Reset the timeout
    clearTimeout(this.typingTimeout);
    
    // Stop typing after 2 seconds of inactivity
    this.typingTimeout = setTimeout(() => {
      this.stopTyping();
    }, 2000);
  }

  startTyping() {
    this.isTyping = true;
    const typingNotification = new CometChat.TypingIndicator(
      this.receiverID,
      this.receiverType
    );
    CometChat.startTyping(typingNotification);
  }

  stopTyping() {
    this.isTyping = false;
    const typingNotification = new CometChat.TypingIndicator(
      this.receiverID,
      this.receiverType
    );
    CometChat.endTyping(typingNotification);
  }

  // Call this when message is sent
  onMessageSent() {
    clearTimeout(this.typingTimeout);
    if (this.isTyping) {
      this.stopTyping();
    }
  }
}

// Usage
const typingHandler = new TypingHandler("user_uid", CometChat.RECEIVER_TYPE.USER);

// In your input field
inputField.addEventListener("input", () => {
  typingHandler.onInputChange();
});

// When sending message
sendButton.addEventListener("click", () => {
  sendMessage();
  typingHandler.onMessageSent();
});

UI Display Example

// Track who is typing
const typingUsers = new Map();

CometChat.addMessageListener(
  "TYPING_LISTENER",
  new CometChat.MessageListener({
    onTypingStarted: (typingIndicator) => {
      const sender = typingIndicator.getSender();
      typingUsers.set(sender.getUid(), sender.getName());
      updateTypingUI();
    },
    onTypingEnded: (typingIndicator) => {
      const sender = typingIndicator.getSender();
      typingUsers.delete(sender.getUid());
      updateTypingUI();
    }
  })
);

function updateTypingUI() {
  const names = Array.from(typingUsers.values());
  
  if (names.length === 0) {
    typingLabel.textContent = "";
  } else if (names.length === 1) {
    typingLabel.textContent = `${names[0]} is typing...`;
  } else if (names.length === 2) {
    typingLabel.textContent = `${names[0]} and ${names[1]} are typing...`;
  } else {
    typingLabel.textContent = "Several people are typing...";
  }
}

React Hook Example

import { useEffect, useState, useCallback, useRef } from "react";
import { CometChat } from "@cometchat/chat-sdk-javascript";

export function useTypingIndicator(receiverID, receiverType) {
  const [typingUsers, setTypingUsers] = useState(new Map());
  const isTypingRef = useRef(false);
  const timeoutRef = useRef(null);

  // Listen for typing events
  useEffect(() => {
    const listenerID = `typing_${receiverID}`;

    CometChat.addMessageListener(
      listenerID,
      new CometChat.MessageListener({
        onTypingStarted: (indicator) => {
          const sender = indicator.getSender();
          setTypingUsers((prev) => {
            const next = new Map(prev);
            next.set(sender.getUid(), sender.getName());
            return next;
          });
        },
        onTypingEnded: (indicator) => {
          const sender = indicator.getSender();
          setTypingUsers((prev) => {
            const next = new Map(prev);
            next.delete(sender.getUid());
            return next;
          });
        }
      })
    );

    return () => {
      CometChat.removeMessageListener(listenerID);
      if (timeoutRef.current) {
        clearTimeout(timeoutRef.current);
      }
      // Stop typing on unmount
      if (isTypingRef.current) {
        stopTyping();
      }
    };
  }, [receiverID]);

  const startTyping = useCallback(() => {
    if (!isTypingRef.current) {
      isTypingRef.current = true;
      const indicator = new CometChat.TypingIndicator(receiverID, receiverType);
      CometChat.startTyping(indicator);
    }

    // Reset timeout
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
    }

    // Auto-stop after 2 seconds of inactivity
    timeoutRef.current = setTimeout(() => {
      stopTyping();
    }, 2000);
  }, [receiverID, receiverType]);

  const stopTyping = useCallback(() => {
    if (isTypingRef.current) {
      isTypingRef.current = false;
      const indicator = new CometChat.TypingIndicator(receiverID, receiverType);
      CometChat.endTyping(indicator);
    }
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
    }
  }, [receiverID, receiverType]);

  // Format typing text
  const typingText = useCallback(() => {
    const names = Array.from(typingUsers.values());
    if (names.length === 0) return "";
    if (names.length === 1) return `${names[0]} is typing...`;
    if (names.length === 2) return `${names[0]} and ${names[1]} are typing...`;
    return "Several people are typing...";
  }, [typingUsers]);

  return {
    typingUsers,
    typingText: typingText(),
    isAnyoneTyping: typingUsers.size > 0,
    onInputChange: startTyping,
    onMessageSent: stopTyping
  };
}

// Usage in component
function ChatInput({ receiverID }) {
  const [message, setMessage] = useState("");
  const { typingText, isAnyoneTyping, onInputChange, onMessageSent } = 
    useTypingIndicator(receiverID, CometChat.RECEIVER_TYPE.USER);

  const handleChange = (e) => {
    setMessage(e.target.value);
    onInputChange();
  };

  const handleSend = async () => {
    if (!message.trim()) return;
    // Send message...
    onMessageSent();
    setMessage("");
  };

  return (
    <div>
      {isAnyoneTyping && <div className="typing-indicator">{typingText}</div>}
      <input
        value={message}
        onChange={handleChange}
        placeholder="Type a message..."
      />
      <button onClick={handleSend}>Send</button>
    </div>
  );
}

Best Practices

Don’t send startTyping on every keystroke. Use a debounce to avoid flooding the server.
Automatically call endTyping after 2-3 seconds of no input to handle cases where users abandon typing.
Always call endTyping when a message is sent to immediately clear the typing indicator.
Call endTyping when the chat component unmounts to clean up any active typing state.

Next Steps