Skip to content
1 change: 1 addition & 0 deletions src/data/languages/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const languageKeys = [
'javascript',
'typescript',
'react',
'java',
'ruby',
Expand Down
4 changes: 4 additions & 0 deletions src/data/nav/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ export default {
name: 'Share media',
link: '/docs/chat/rooms/media',
},
{
name: 'Replies',
link: '/docs/chat/rooms/replies',
},
],
},
{
Expand Down
201 changes: 201 additions & 0 deletions src/pages/docs/chat/rooms/replies.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
---
title: "Implement replies"
meta_description: "Add reply functionality to messages in a chat room."
meta_keywords: "ably chat, message replies, chat replies, javascript chat replies, typescript chat replies, chat metadata"
---

Implement replies by storing reference information in message metadata when users respond to other messages.

Use the [metadata](/docs/chat/rooms/messages#structure) field to store reply information when [sending a message](/docs/chat/rooms/messages#send). Use the [get()](/docs/chat/rooms/messages#get-by-serial) method to fetch individual parent messages by serial, and [subscribe](/docs/chat/rooms/messages#subscribe) to receive new messages including replies in the room.

## Message fields

Use these message fields to implement replies:
- `serial`: Unique message identifier
- `clientId`: Sender ID
- `text`: Message content
- `createdAt`: Timestamp
- `metadata`: Custom data storage

## Store reply information <a id="store-information"/>

When a user replies to a message, extract and store the parent message details:

<Code>
```javascript
function prepareReply(parentMessage) {
return {
serial: parentMessage.serial,
createdAt: parentMessage.createdAt.getTime(),
clientId: parentMessage.clientId,
previewText: parentMessage.text.substring(0, 140)
};
}
```

```react
const prepareReply = (parentMessage) => {
return {
serial: parentMessage.serial,
createdAt: parentMessage.createdAt.getTime(),
clientId: parentMessage.clientId,
previewText: parentMessage.text.substring(0, 140)
};
};
```
</Code>

## Send reply <a id="send-reply"/>

Include the parent message information in the metadata when sending:

<Code>
```javascript
async function sendReply(replyToMessage, replyText) {
const metadata = {
reply: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use prepareReply here?

serial: replyToMessage.serial,
timestamp: replyToMessage.createdAt.getTime(),
clientId: replyToMessage.clientId,
previewText: replyToMessage.text.substring(0, 140)
}
};

await room.messages.send({
text: replyText,
metadata: metadata
});
}
```

```react
import { useMessages } from '@ably/chat/react';

const ReplyComponent = ({ messageToReplyTo }) => {
const { sendMessage } = useMessages();

const sendReply = async (replyText) => {
const metadata = {
reply: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, use the helper defined earlier?

serial: messageToReplyTo.serial,
createdAt: messageToReplyTo.createdAt.getTime(),
clientId: messageToReplyTo.clientId,
previewText: messageToReplyTo.text.substring(0, 140)
}
};

await sendMessage({
text: replyText,
metadata: metadata
});
};

return (
<div>
<button onClick={() => sendReply("My reply")}>Send Reply</button>
</div>
);
};
```
</Code>

## Display replies <a id="handle-received-messages"/>

Check incoming messages for reply metadata and display accordingly:

<Code>
```javascript
room.messages.subscribe((messageEvent) => {
const message = messageEvent.message;

if (message.metadata?.reply) {
const replyData = message.metadata.reply;
const parentMessage = localMessages.find(msg => msg.serial === replyData.serial);

if (parentMessage) {
console.log(`Reply to ${parentMessage.clientId}: ${parentMessage.text}`);
} else {
console.log(`Reply to ${replyData.clientId}: ${replyData.previewText}`);
}
}

console.log(`Message: ${message.text}`);
});
```

```react
import { useMessages } from '@ably/chat/react';
import { ChatMessageEventType } from '@ably/chat';

const MessageList = () => {
const [messages, setMessages] = useState([]);

useMessages({
listener: (event) => {
if (event.type === ChatMessageEventType.Created) {
setMessages(prev => [...prev, event.message]);
}
}
});

const findParentMessage = (replyData) => {
return messages.find(msg => msg.serial === replyData.serial);
};

return (
<div>
{messages.map(message => (
<div key={message.serial}>
{message.metadata?.reply && (
<div>
Replying to: {message.metadata.reply.previewText}
</div>
)}
<div>{message.text}</div>
</div>
))}
</div>
);
};
```
</Code>

## Fetch missing parent messages <a id="find-parent-messages"/>

If a parent message isn't in local state, fetch it directly using its serial:

<Code>
```javascript
async function fetchParentMessage(replyData) {
const message = await room.messages.get(replyData.serial);
return message;
}
```

```react
const FetchParentMessage = ({ replyData }) => {
const [parentMessage, setParentMessage] = useState();

useEffect(() => {
const fetchMessage = async () => {
const message = await room.messages.get(replyData.serial);
setParentMessage(message);
};

fetchMessage();
}, [replyData]);

return parentMessage ? (
<div>{parentMessage.text}</div>
) : null;
};
```
</Code>

## Considerations

Keep in mind that:
- Old messages may not be available depending on message persistence settings.
- Messages can be updated, potentially removing reply references.
- Metadata is not server-validated.
- Nested replies can be complex and expensive to implement, so consider limiting reply depth.