Guide Overview:
This guide demonstrates how to implement thread replies using the CometChat React UI Kit v6. While thread reply functionality isn’t available out of the box, this tutorial will walk you through building a custom component to handle threaded conversations.
Note: This guide can be followed to implement thread replies functionality in v5 UI Kit as well.
Step 1: Set up state to track the selected parent message
To enable thread replies, you first need to store the message that a user chooses to reply to. This message becomes the parent message of the thread.
const [selectedThreadMsg, setSelectedThreadMsg] = useState<CometChat.BaseMessage | null>(null);
Step 2: Handle Thread Reply Click Action
Define a function that will handle the onClick
event when a user clicks to view or reply to thread messages:
const getOnThreadRepliesClick = (message: CometChat.BaseMessage) => {
// Store the message selected for replying in the state
setSelectedThreadMsg(message);
}
Step 3: Pass the Handler to <CometChatMessageList />
Pass the above function as a prop to the main message list component to enable thread reply clicks:
<CometChatMessageList
user={selectedUser}
group={selectedGroup}
// This triggers when a reply is clicked
onThreadRepliesClick={getOnThreadRepliesClick}
/>
Step 4: Conditionally Render the Custom Thread Component
To enable threaded messaging, you need to render the custom Thread component
only when a message has been selected for replying.
{selectedThreadMsg && (
<Thread
selectedThreadMsg={selectedThreadMsg}
setSelectedThreadMsg={setSelectedThreadMsg}
selectedUser={selectedUser}
selectedGroup={selectedGroup}
/>
)}
Step 5: Build the thread component
To implement threaded messaging, you’ll need to assemble several UIKit components, each playing a specific role in displaying and managing thread conversations.
Key Points:
CometChatThreadHeader:
Displays the parent message at the top of the thread view, giving users context on what they’re replying to.CometChatMessageList:
Use with aparentMessageId
and a custommessagesRequestBuilder
to fetch and display only the replies associated with the selected parent message.CometChatMessageComposer:
also configure the composer with the same parentMessageId, this allows users to compose and send replies directly within the thread view.
Thread Component:
import {
CometChatMessageComposer,
CometChatMessageList,
CometChatThreadHeader,
CometChatUIKit,
} from "@cometchat/chat-uikit-react";
import { CometChat } from "@cometchat/chat-sdk-javascript";
import "./Thread.css";
interface ThreadProps {
selectedThreadMsg: CometChat.BaseMessage | null;
setSelectedThreadMsg: (msg: CometChat.BaseMessage | null) => void;
selectedUser: CometChat.User | undefined;
selectedGroup: CometChat.Group | undefined;
}
const Thread = ({
selectedThreadMsg,
setSelectedThreadMsg,
selectedUser,
selectedGroup,
}: ThreadProps) => {
// Build message request to fetch thread messages, excluding action messages
const threadMsgBuilder = () => {
return new CometChat.MessagesRequestBuilder()
.setCategories(
CometChatUIKit.getDataSource()
.getAllMessageCategories()
.filter((cat) => cat !== "action")
)
.setTypes(CometChatUIKit.getDataSource().getAllMessageTypes())
.hideReplies(true)
.setLimit(20)
.setParentMessageId(selectedThreadMsg ? selectedThreadMsg.getId() : 0);
};
return (
<div className="thread-container">
{/* Thread header with close button */}
{selectedThreadMsg && (
<CometChatThreadHeader
parentMessage={selectedThreadMsg}
onClose={() => setSelectedThreadMsg(null)}
/>
)}
{/* Thread messages list */}
<CometChatMessageList
user={selectedUser}
group={selectedGroup}
parentMessageId={selectedThreadMsg?.getId()}
messagesRequestBuilder={threadMsgBuilder()}
/>
{/* Composer sends messages as replies in the thread */}
<CometChatMessageComposer
user={selectedUser}
group={selectedGroup}
parentMessageId={selectedThreadMsg?.getId()}
/>
</div>
);
};
export default Thread;
Main Component: Integrate Thread Component
Import and render the <Thread/>
component inside your <App/>
component to display thread conversations.
import { useState } from "react";
import {
CometChatMessageComposer,
CometChatMessageHeader,
CometChatMessageList,
} from "@cometchat/chat-uikit-react";
import { CometChat } from "@cometchat/chat-sdk-javascript";
import { CometChatSelector } from "../CometChatSelector/CometChatSelector";
import "./home.css";
import "@cometchat/chat-uikit-react/css-variables.css";
import Thread from "../thread/Thread";
function App() {
const [selectedUser, setSelectedUser] = useState<CometChat.User | undefined>(undefined);
const [selectedGroup, setSelectedGroup] = useState<CometChat.Group | undefined>(undefined);
const [selectedCall, setSelectedCall] = useState<CometChat.Call | undefined>(undefined);
// State to store the selected message to reply
const [selectedThreadMsg, setSelectedThreadMsg] = useState<CometChat.BaseMessage | null>(null);
const getOnThreadRepliesClick = (message: CometChat.BaseMessage) => {
setSelectedThreadMsg(message);
};
return (
<div className="conversations-with-messages">
<div className="conversations-wrapper">
<CometChatSelector
onSelectorItemClicked={(activeItem) => {
let item = activeItem;
if (activeItem instanceof CometChat.Conversation) {
item = activeItem.getConversationWith();
}
if (item instanceof CometChat.User) {
setSelectedUser(item as CometChat.User);
setSelectedCall(undefined);
setSelectedGroup(undefined);
} else if (item instanceof CometChat.Group) {
setSelectedUser(undefined);
setSelectedGroup(item as CometChat.Group);
setSelectedCall(undefined);
} else if (item instanceof CometChat.Call) {
setSelectedUser(undefined);
setSelectedGroup(undefined);
setSelectedCall(item as CometChat.Call);
}
}}
/>
</div>
{selectedUser || selectedGroup || selectedCall ? (
<div className="messages-wrapper">
<CometChatMessageHeader user={selectedUser} group={selectedGroup} />
<CometChatMessageList
user={selectedUser}
group={selectedGroup}
onThreadRepliesClick={getOnThreadRepliesClick}
/>
<CometChatMessageComposer
user={selectedUser}
group={selectedGroup}
/>
</div>
) : (
<div className="empty-conversation">Select Conversation to start</div>
)}
{/* Render Thread component when a thread message is selected */}
{selectedThreadMsg && (
<Thread
selectedThreadMsg={selectedThreadMsg}
setSelectedThreadMsg={setSelectedThreadMsg}
selectedUser={selectedUser}
selectedGroup={selectedGroup}
/>
)}
</div>
);
}
export default App;
CometChatSelector component:
import { useEffect, useState } from "react";
import {
Conversation,
Group,
User,
CometChat,
} from "@cometchat/chat-sdk-javascript";
import {
CometChatConversations,
CometChatUIKitLoginListener,
} from "@cometchat/chat-uikit-react";
import "./CometChatSelector.css";
// Define the props for the CometChatSelector component
interface SelectorProps {
onSelectorItemClicked?: (
input: User | Group | Conversation,
type: string
) => void;
onHide?: () => void;
onNewChatClicked?: () => void;
}
// CometChatSelector component
export const CometChatSelector = (props: SelectorProps) => {
// Destructure props with a default function for onSelectorItemClicked
const { onSelectorItemClicked = () => {} } = props;
// State to store the logged-in user
const [loggedInUser, setLoggedInUser] = useState<CometChat.User | null>();
// State to track the currently selected item (it can be a Conversation, User, or Group)
const [activeItem, setActiveItem] = useState<
CometChat.Conversation | CometChat.User | CometChat.Group | undefined
>();
// useEffect hook to fetch and set the logged-in user
useEffect(() => {
const loggedInUser = CometChatUIKitLoginListener.getLoggedInUser();
setLoggedInUser(loggedInUser);
}, [loggedInUser]); // Dependency on loggedInUser causes unnecessary re-renders
return (
<>
{/* Render chat conversations if a user is logged in */}
{loggedInUser && (
<>
<CometChatConversations
activeConversation={
activeItem instanceof CometChat.Conversation
? activeItem
: undefined
}
onItemClick={(e) => {
setActiveItem(e); // Update the active item when an item is clicked
onSelectorItemClicked(e, "updateSelectedItem"); // Notify parent component
}}
/>
</>
)}
</>
);
};