How to Add Reactions and Emoji to Messages with the PubNub Chat SDK
Introduction to the PubNub Chat SDK
The PubNub Chat SDK, available for TypeScript and JavaScript applications, exposes a set of APIs designed to make it easy to add powerful and flexible chat features to your app with minimum development. Chat options like quoting, mentioning users, channel references, threads, read receipts, and typing indicators are supported natively by the SDK, letting you build a full-fledged app quickly.
Please refer to our documentation and sample chat app to get started with the Chat SDK. Our tutorial will walk you through the basic functionality of the Chat SDK and touch on some more advanced features whilst our hosted demo will show the Chat SDK in action.
This how-to is part of a series of posts diving into some of the more powerful features of the PubNub Chat SDK. The series can be read in any order, but the list of related articles is below:
- How to Manage User Channel Membership with the Chat SDK
- How to Add Reactions and Emojis to Messages with the Chat SDK
- How to Create Threads and Quote Messages with the Chat SDK
- How to Mention Users and Channels with the Chat SDK
To use the Chat SDK, please ensure you enable App Context on your PubNub keyset.
Add Reactions and Emojis to Messages
Being able to react to a message is a fundamental part of many chat implementations, for example, giving a message a thumbs up or a smiley face emoji. You might see the terminology ‘message reactions’ and ‘emoji’ used interchangeably, but understand that they are describing the same thing - this is not sending an emoji as part of the message.
Prerequisites
Ensure you have an instance of the Chat object instantiated in your app
const chat = await Chat.init({
publishKey: "YOUR_PUBLISH_KEY",
subscribeKey: "YOUR_SUBSCRIBE_KEY",
userId: "YOUR_USER_ID",
})// include the PubNub Unity Chat SDK package in your code before initializing the client.
using PubnubChatAPI.Entities;
using PubnubChatApi.Entities.Data;
// initialize your Unity Chat SDK client using your app keys from the Admin Portal and a unique user ID for your client
var chat = new Chat(new PubnubChatConfig(
"YOUR_PUBLISH_KEY",
"YOUR_SUBSCRIBE_KEY",
"YOUR_USER_ID")
);PubnubChatSubsystem* PubnubChatSubsystem = GameInstance->GetSubsystem<UPubnubChatSubsystem>();
UPubnubChat* Chat = PubnubChatSubsystem->InitChat(
"YOUR_PUBLISH_KEY",
"YOUR_SUBSCRIBE_KEY",
"YOUR_USER_ID"
);val chatConfig = ChatConfiguration()
val pnConfiguration = PNConfiguration.builder(userId = UserId("myUserId"),
subscribeKey = "mySubscribeKey").build()
Chat.init(chatConfig, pnConfiguration).async { result ->
result.onSuccess { chat: Chat ->
println("Chat successfully initialized")
}.onFailure { exception: PubNubException ->
println("Exception initialising chat: ${exception.message}")
}
}let pubNubConfiguration = PubNubConfiguration(
publishKey: "your-publish-key",
subscribeKey: "your-subscribe-key",
userId: "your-user-id"
)
let yourChat = ChatImpl(
chatConfiguration: ChatConfiguration(),
pubNubConfiguration: pubNubConfiguration
)
yourChat.initialize { result in
switch result {
case .success(_):
debugPrint("Initialized successfully!")
case .failure(let error):
debugPrint("Error during initialization: \(error)")
}
}There are a lot of possible parameters you can pass to the Chat SDK, but for message reactions, you do not need anything more than the standard publish key, subscribe key, and user ID. If all of this is new to you, and you’re not sure where to get started, please check out the initial configuration section in our documentation.
Message Reactions
To express a reaction to a message, call the message.toggleReaction() API.
// add the "thumb up" emoji to that message
const newMsg = await message.toggleReaction("👍")// add the "thumbs up" emoji to the last message
message.ToggleReaction("\u{1F44D}");// add the "thumbs up" emoji to that message
Message->ToggleReaction("\u{1F44D}");message?.toggleReaction("\u1F44D").async { reactionResult ->
reactResult.onSuccess { reactedMessage ->
// handle success
}.onFailure {
// handle failure
}
}message.toggleReaction(reaction: "\u{1F44D}") { reactionResult in
switch reactionResult {
case let .success(updatedMessage):
print("Reaction added successfully to message: \(updatedMessage)")
case let .failure(error):
print("Failed to add reaction: \(error)")
}
}Message is a fundamental type within the Chat SDK, and you are most likely rendering your messages within a list, but how the user chooses a reaction will depend on your UX.
There are no setReaction(...) or deleteReaction(...) methods; the only way to apply or remove a reaction to a message is to use the toggleReaction() API. ToggleReaction() will return a new copy of the message, which should replace the existing messages in the user’s list.
To retrieve reactions, the Chat SDK can either notify you via callback, or you can query a specific message to ask what reactions it has applied.
Querying a message’s reactions
To query the reactions applied to a single message, use the message.reactions API:
message.reactions["👍"]var reactions = message.Reactions();TArray<FPubnubMessageAction> Reactions = Message->Reactions();val reactions = message.reactions
if (reactions.isNotEmpty()) {
reactions.forEach { reaction ->
// Handle each reaction
}
} else {
// No reactions on this message
}if latestMessage.reactions.isEmpty {
print("No reactions found for the latest message")
} else {
print("Reactions for the latest message: \(latestMessage.reactions)")
}Remember, a message can have multiple emoji reactions (🙂🥰🥳), and multiple users might have applied each emoji. The return value from ‘message.reactions’ therefore, is an Object, with each key being a different emoji. The value associated with each key is an array containing all the user IDs who applied the emoji, along with the time the reaction was applied.
{
"🥳": [
{
"uuid": "user-1",
"actionTimetoken": "16983289196082900"
}
],
"🙂": [
{
"uuid": "user-1",
"actionTimetoken": "16983290238341020"
},
{
"uuid": "user-2",
"actionTimetoken": "16983290238341020"
}
]
}Receiving notifications when a message’s reactions update
To be notified when a message’s reactions change, the Chat SDK exposes the streamUpdates() and streamUpdatesOn() methods. StreamUpdates() can be invoked on a single message object to register a callback invoked when only that message’s reactions change. Still, it is more common that you want to be notified when any message in a conversation or thread receives a reaction, and for this more prolific use case, you should use streamUpdatesOn().
StreamUpdatesOn() is a static method of the Message class, also inherited in the ThreadMessage class. It is common to have a conversation in a single channel displayed in one view, but if you expand a message thread, that thread is displayed in a separate view. In this example, you might use Message.streamUpdatesOn() for the main view and ThreadMessage.streamUpdatesOn() for the thread view.
Message.streamUpdatesOn(messages, (messages) => {
console.log("Updated messages: ", messages)
})// This code snippet registers for updates on the last 10 received messages from some
// hardcoded timetoken
var messages = channel.GetMessageHistory(startTimetoken: "15343325214676133", count: 10);
List<string> timetokens = new List<string>();
// get the timetokens
for (int i = 0; i < messages.Count; i++)
{
// Get the time token of the current message
string timeToken = messages[i].TimeToken;
// Add the time token to the list
timetokens.Add(timeToken);
}
void OnMessageUpdatedHandler(Message message)
{
Console.WriteLine($"Message updated: {string.Join(", ", message)}");
}
chat.AddListenerToMessagesUpdate(
channelId: "support",
messageTimeTokens: timetokens,
listener: OnMessageUpdatedHandler
);UPubnubChannel* Channel = Chat->GetChannel("group-chat-1");
// Assume Messages is a TArray<UPubnubMessage*> of messages in the channel
// Create a pubnub response delegate
// you MUST implement your own callback function to handle the response
FOnPubnubMessageStreamUpdateReceived StreamUpdatesResponse;
StreamUpdatesResponse.BindDynamic(this, &AMyActor::OnStreamUpdatesOnMessageResponseReceived);
Message->StreamUpdatesOn(Messages, MessagesStreamUpdatesResponse);// Assuming messages is a Collection<Message> Object.
// Likely you have this to hold the state of messages in the current chat window.
val autoCloseable = Message.streamUpdatesOn(messages = messages) { updatedMessages ->
updatedMessages.forEach { updatedMessage ->
// Message has updated
}
// update your application state
messages = updatedMessages.toList()
}// Code example taken from Messages.streamUpdatesOn() documentation
// Stream updates on the fetched messages
let autoCloseable = MessageImpl.streamUpdatesOn(messages: messages) { updatedMessages in
debugPrint("Received updates for messages")
for updatedMessage in updatedMessages {
debugPrint("Message with timetoken \(updatedMessage.timetoken) updated")
// Handle each updated message (e.g., reaction updates, content change)
}
}
// Store "autoCloseable" if you need to stop streaming later
self?.streamingAutoCloseable = autoCloseableThe value returned in the streamUpdates()/streamUpdatesOn() callback is the updated message/messages instance, so be sure to update your application’s UI accordingly.
Putting it all together
One possible implementation would be to store the current conversation messages in the application state, which is updated with streamMessages(). The current messages displayed to the user are rendered from that application state and as part of that render function, message.reactions will determine which emoji should be displayed next to each message. If the user interacts with the message to add a reaction to it, use toggleReaction to notify the Chat SDK and update the application state
You can see this behavior in the short demo below. This is a real, live, working demo, so feel free to launch the demo in multiple tabs to see real-time reactions shared between participants:
Interactive Demo
The code that drives this demo is available on GitHub, but the key points are:
Click handler when a user left or right clicks a message:
// Code to power embedded demo, above
async function handleClick(message: Message, reaction: string)
{
await message.toggleReaction(reaction)
}When we receive a callback that messages have changed, update our UX
// Code to power embedded demo, above
useEffect(() => {
if (!messages.length) return
return Message.streamUpdatesOn(messages, setMessages)
}, [messages])Display the reaction against the message. Note that the deliberately simple implementation in this shared demo is effectively a shared reaction where any user can add or remove it. Your implementation will likely display a number next to each emoji to indicate how many people have reacted.:
// Code to power embedded demo, above
{message.reactions["🙂"] && message.reactions["🙂"].length !== 0 ? "🙂":""}
{message.reactions["🥳"] && message.reactions["🥳"].length !== 0 ? "🥳":""}Historical reactions
If you have persistence enabled on your PubNub keyset, messages will be stored by PubNub and can be retrieved through the Message History.
The Chat SDK will automatically persist message reactions in history for you, allowing you to access historical reactions without any additional steps. If you previously used our traditional PubNub SDKs, you might be aware that to retrieve historical message reactions, you needed to specify ‘includeMessageActions’ when making the call, but the Chat SDK makes things simpler.
This is illustrated with the following code sample taken directly from the documentation
// reference the "support" channel with the message you want to check
const channel = await chat.getChannel("support")
// reference the last message on that "support" channel
const message = await channel.getHistory({count: 1}).messages[0]
// list all thumbs-up reactions to the message
console.log(message.reactions["👍"])// reference the "support" channel and ensure it's found
if (chat.TryGetChannel("support", out var channel))
{
Console.WriteLine($"Found channel with name {channel.Name}");
// get the message history with the desired count
var messageHistory = channel.GetMessageHistory(null, null, 1);
// get the last message from the returned list
var lastMessage = messageHistory.Messages.FirstOrDefault();
if (lastMessage != null)
{
// output all reactions added to the last message
var reactions = lastMessage.Reactions();
foreach (var reaction in reactions)
{
Console.WriteLine($"Reaction: {reaction}");
}
}
else
{
Console.WriteLine("No messages found in the channel history.");
}
}
else
{
Console.WriteLine("Support channel not found.");
}UPubnubChannel* Channel = Chat->GetChannel("support");
// Define parameters for the GetHistory method
int Limit = 10;
FString StartTimetoken = TEXT("15343325214676133");
FString EndTimetoken; // Empty FString means no end timetoken will be used
// Fetch historical messages
TArray<UPubnubMessage*> Messages = Channel->GetHistory(Limit, StartTimetoken, EndTimetoken);
// Get the reactions
TArray<FPubnubMessageAction> Reactions = Messages[0]->Reactions();// Assuming a channel object (e.g. obtained through chat.getChannel(channelId))
val channel: Channel
channel.getHistory(count = 1).async { result ->
result.onSuccess { history ->
history.messages.firstOrNull()?.let { message ->
message.toggleReaction("👍").async {
it.onSuccess { newMessage ->
// success - the returned message needs to replace an existing message in your app’s message list
}.onFailure {
// failure
}
}
}
}.onFailure {
// handle failure
}
}chat?.getChannel(channelId: "support") { result in
switch result {
case let .success(channel):
if let supportChannel = channel {
print("Fetched support channel metadata with ID: \(supportChannel.id)")
// Get the latest message from the "support" channel's history
supportChannel.getHistory(count: 1) { historyResult in
switch historyResult {
case let .success(response):
if let latestMessage = response.messages.first {
print("Latest message: \(latestMessage)")
// Check if the current user added the "thumb up" emoji reaction
let userAddedThumbUp = latestMessage.hasUserReaction(reaction: "\u{1F44D}")
if userAddedThumbUp {
print("The current user has added the 'thumb up' emoji (👍) to the latest message.")
} else {
print("The current user has not added the 'thumb up' emoji (👍) to the latest message.")
}
} else {
print("No messages found in history")
}
case let .failure(error):
print("Failed to fetch message history: \(error)")
}
}
} else {
print("Support channel not found")
}
case let .failure(error):
print("Failed to fetch support channel metadata: \(error)")
}
}Demo: Add Reactions with our React Native demo on Mobile
You can play with this feature using our Chat SDK Demo for Mobile, available as a hosted demo with full source code available on GitHub. You should also see the demo rendered in an iFrame at the bottom of this section.
Follow these steps to use message reactions in our demo:
- Log in to the application, choosing a random user id for each of the two devices.
- Start a conversation from the second device, selecting the user logged into the first device.
- Add some messages to the conversation.
- Long press on one of the messages.
- Select an emoji to react.
- Repeat to add a few other emoji
- Notice that if you back out of the conversation or even log out of and back into the app, the reactions are retained and read from the message history.