```javascript
const {unsubscribe} = room.messages.subscribe((event) => {
console.log(event.message);
});
```
```react
const MyComponent = () => {
useMessages({
listener: (event) => {
console.log('Received message: ', event.message);
},
});
return ...;
};
```
```swift
let messagesSubscription = try await room.messages.subscribe()
for await message in messagesSubscription {
print("Message received: \(message)")
}
```
```kotlin
val subscription = room.messages.subscribe { messageEvent: ChatMessageEvent ->
println(messageEvent.message.toString())
}
```
```jetpack
const MyComponent = () => {
const { sendMessage } = useMessages();
const handleMessageSend = () => {
sendMessage({ text: 'Hello, World!' });
};
return (
);
};
```
```swift
let message = try await room.messages.send(params: .init(text: "hello"))
```
```kotlin
room.messages.send(text = "hello")
```
```jetpack
const MyComponent = () => {
const { getMessage } = useMessages();
const handleMessageGet = () => {
getMessage('01726232498871-001@abcdefghij:001');
};
return (
);
};
```
```swift
let message = try await room.messages.get(withSerial: "01726232498871-001@abcdefghij:001")
```
```kotlin
val message = room.messages.get("01726232498871-001@abcdefghij:001")
```
```jetpack
val message = room.messages.get("01726232498871-001@abcdefghij:001")
```
## Update a message
```javascript
const message: Message
const updatedMessage = message.copy({text: "my updated text"})
await room.messages.update(updatedMessage.serial, updatedMessage, { description: "Message update by user" });
```
```react
const MyComponent = () => {
const { updateMessage } = useMessages();
const [message, setMessage] = useState();
const handleMessageUpdate = (msg: Message) => {
updateMessage(msg.serial, msg.copy({ text: "my updated text" }), { description: "Message update by user" })
.then((updatedMsg: Message) => {
console.log('Message updated:', updatedMsg);
})
.catch((error) => {
console.error('Error updating message: ', error);
});
};
return (
);
};
```
```swift
let originalMessage: Message
let updatedMessage = try await room.messages.update(
forSerial: originalMessage.serial,
params: .init(text: "my updated text"),
details: .init(description: "Message update by user")
)
```
```kotlin
val originalMessage: Message
val updatedMessage = room.messages.update(
originalMessage.copy(text = "my updated text"),
operationDescription = "Message update by user",
)
```
```jetpack
const {unsubscribe} = room.messages.subscribe((event) => {
switch (event.type) {
case ChatMessageEventType.Created:
console.log('Received message: ', event.message);
break;
case ChatMessageEventType.Updated:
const existing = myMessageList.find(msg => msg.serial === event.message.serial);
if (existing && event.message.version.serial <= existing.version.serial) {
// We've already received a more recent update, so this one can be discarded.
return;
}
console.log('Message updated: ', event.message);
break;
default:
break;
}
});
```
```react
const MyComponent = () => {
useMessages({
listener: (event) => {
switch (event.type) {
case ChatMessageEventType.Created:
console.log('Received message: ', event.message);
break;
case ChatMessageEventType.Updated:
const existing = myMessageList.find(msg => msg.serial === event.message.serial);
if (existing && event.message.version.serial <= existing.version.serial) {
// We've already received a more recent update, so this one can be discarded.
return;
}
console.log('Message updated: ', event.message);
break;
default:
break;
}
},
});
return ...;
};
```
```swift
let messagesList: [Message]
let messagesSubscription = try await room.messages.subscribe()
for await message in messagesSubscription {
switch message.action {
case .messageCreate:
messagesList.append(message)
case .messageUpdate:
// compare versions to ensure you are only updating with a newer message
if let index = messagesList.firstIndex(where: { $0.serial == message.serial && message.version > $0.version }) {
messagesList[index] = message
}
default:
break
}
}
```
```kotlin
val myMessageList: List
val messagesSubscription = room.messages.subscribe { event ->
when (event.type) {
ChatMessageEventType.Created -> println("Received message: ${event.message}")
ChatMessageEventType.Updated -> myMessageList.find {
event.message.serial == it.serial && event.message.version.serial > it.version.serial
}?.let { println("Message updated: ${event.message}") }
else -> {}
}
}
```
```jetpack
const messageToDelete: Message
await room.messages.delete(messageToDelete.serial, { description: 'Message deleted by user' });
```
```react
const MyComponent = () => {
const { deleteMessage } = useMessages();
const [message, setMessage] = useState();
const handleMessageDelete = (msg: Message) => {
deleteMessage(msg.serial, { description: 'Message deleted by user' })
.then((deletedMessage: Message) => {
console.log('Message deleted:', deletedMessage);
})
.catch((error) => {
console.error('Error deleting message: ', error);
});
};
return (
);
};
```
```swift
let messageToDelete: Message
let deletedMessage = try await room.messages.delete(
forSerial: messageToDelete.serial,
params: .init(description: "Message deleted by user")
)
```
```kotlin
val messageToDelete: Message
val deletedMessage = room.messages.delete(
messageToDelete,
operationDescription = "Message deleted by user",
)
```
```jetpack
const {unsubscribe} = room.messages.subscribe((event) => {
switch (event.type) {
case ChatMessageEventType.Created:
console.log('Received message: ', event.message);
break;
case ChatMessageEventType.Deleted:
const existing = myMessageList.find(msg => msg.serial === event.message.serial);
if (existing && event.message.version.serial <= existing.version.serial) {
// We've already received a more recent update, so this one can be discarded.
return;
}
console.log('Message deleted: ', event.message);
break;
default:
break;
}
});
```
```react
const MyComponent = () => {
useMessages({
listener: (event) => {
switch (event.type) {
case ChatMessageEventType.Created:
console.log('Received message: ', event.message);
break;
case ChatMessageEventType.Deleted:
const existing = myMessageList.find(msg => msg.serial === event.message.serial);
if (existing && event.message.version.serial <= existing.version.serial) {
// We've already received a more recent update, so this one can be discarded.
return;
}
console.log('Message deleted: ', event.message);
break;
default:
break;
}
},
});
return ...;
};
```
```swift
let messagesList: [Message]
let messagesSubscription = try await room.messages.subscribe()
for await message in messagesSubscription {
switch message.action {
case .messageCreate:
messagesList.append(message)
case .messageDelete:
// version check ensures the message you are deleting is older
if let index = messagesList.firstIndex(where: { $0.serial == message.serial && message.version > $0.version }) {
messagesList.remove(at: index)
}
default:
break
}
}
```
```kotlin
val myMessageList: List
val messagesSubscription = room.messages.subscribe { event ->
when (event.type) {
ChatMessageEventType.Created -> println("Received message: ${event.message}")
ChatMessageEventType.Deleted -> myMessageList.find {
event.message.serial == it.serial && event.message.version.serial > it.version.serial
}?.let { println("Message deleted: ${event.message}") }
else -> {}
}
}
```
```jetpack
const messageA: Message
const messageB: Message
if (messageA.serial < messageB.serial) {
console.log('messageA occurred before messageB');
} else if (messageA.serial > messageB.serial) {
console.log('messageA occurred after messageB');
} else {
console.log('messageA and messageB are concurrent (the same message)');
}
```
### Ordering updates and deletes
Applying an action to a message produces a new version, which is uniquely identified by the `version.serial` property. When two message instances share the same `serial` they represent the same chat message, but they can represent different versions. Lexicographically sorting the two message instances by the `version.serial` property gives the global order of the message versions: the message instance with a greater `version.serial` is newer, the message instance with a lower `version.serial` is older, and if their `version.serial` is equal then they are the same version.
Update and Delete events provide the message payload without message reactions. To correctly use message reactions, always use the [`with()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Message.html#with) method to apply the event to the message instance.
## Keep messages updated using with()
The [`Message`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Message.html) object has a method [`with`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Message.html#with) that takes a [`MessageEvent`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.MessageEvent.html), automatically compares version serials, and returns the newest `Message` instance. For updates and deletes, if `message.with(event)` is called with an `event` that has an older `version.serial` than the `message`, then the `message` is returned unchanged. If it is called with a newer event (greater `version.serial`), then the message from the event is returned. For message reaction events, the reactions will be correctly applied to the returned message.
`Message.with()` also ensures that reactions from existing messages are copied over to the new message instance in the case of UPDATEs or DELETEs.
Example usage to keep a list of messages updated:
```javascript
let myMessageList: Message[];
// For messages (create, update, delete)
room.messages.subscribe((event) => {
switch (event.type) {
case ChatMessageEventType.Created:
myMessageList.push(event.message);
break;
case ChatMessageEventType.Updated:
case ChatMessageEventType.Deleted:
const idx = myMessageList.findIndex((msg) => msg.serial === event.message.serial);
if (idx !== -1) {
myMessageList[idx] = myMessageList[idx].with(event);
}
break;
default:
break;
}
});
// And for message reactions
room.messages.reactions.subscribe((event) => {
const idx = myMessageList.findIndex((msg) => msg.serial === event.messageSerial);
if (idx !== -1) {
myMessageList[idx] = myMessageList[idx].with(event);
}
});
```
```react
const MyComponent = () => {
// we use {list: []} to avoid copying the full array with every change
// but still take advantage of React's state change detection
const [ messages, setMessages ] = useState<{list: Message[]}>({list: []});
useMessages({
listener: (event) => {
switch (event.type) {
case ChatMessageEventType.Created:
setMessages((prev) => {
// append new message
prev.list.push(event.message);
// update reference without copying whole array
return { list: prev.list };
});
break;
case ChatMessageEventType.Updated:
case ChatMessageEventType.Deleted:
setMyMessageList((prev) => {
// find existing message to apply update or delete to
const existing = prev.list.findIndex((msg) => msg.serial === event.message.serial);
if (existing === -1) {
return prev; // no change if not found
}
const newMsg = existing.with(event);
if (newMsg === existing) {
// with() returns the same object if the event is older,
// so in this case no change is needed
return prev;
}
// set new message and update reference without copying whole array
prev.list[existing] = newMsg;
return { list: prev.list };
});
break;
}
},
});
return ...;
};
```