```javascript
const {unsubscribe} = room.typing.subscribe((event) => {
console.log("Currently typing: ", event.currentlyTyping);
});
```
```react
import { useTyping } from '@ably/chat/react';
import { TypingSetEvent } from '@ably/chat';
const MyComponent = () => {
const { currentlyTyping, roomError } = useTyping({
listener: (typingEvent: TypingSetEvent) => {
console.log('Typing event received: ', typingEvent);
},
});
return (
{roomError && Typing Error: {roomError.message}
}
Currently typing: {Array.from(currentlyTyping).join(', ')}
);
};
```
```swift
let typingSubscription = room.typing.subscribe()
for await typing in typingSubscription {
typingInfo = typing.currentlyTyping.isEmpty ? "" :
"Typing: \(typing.currentlyTyping.joined(separator: ", "))..."
}
```
```kotlin
val subscription = room.typing.subscribe { event: TypingSetEvent ->
println("currently typing: ${event.currentlyTyping}")
}
```
```jetpack
import androidx.compose.material.*
import androidx.compose.runtime.*
import com.ably.chat.Room
import com.ably.chat.extensions.compose.collectAsCurrentlyTyping
@Composable
fun TypingComponent(room: Room) {
val currentlyTyping by room.collectAsCurrentlyTyping()
Text("Currently typing: ${currentlyTyping.joinToString(", ")}")
}
```
### Typing event structure
The following is the structure of a typing event:
```json
{
"type": "typing.set.changed",
"currentlyTyping": {
"clemons",
"zoranges",
},
"change": {
"type": "typing.started",
"clientId": "clemons"
}
}
```
The following are the properties of a typing event:
| Property | Description | Type |
|----------|-------------|------|
| type | The type of the event. | String |
| currentlyTyping | A set of all users currently typing. | Set |
| change | The single change that resulted in the event. | Object |
| | `type`: The type of change that occurred. | String |
| | `clientId`: The `clientId` of the user that triggered the change. | String |
You can use the size of the `currentlyTyping` set to decide whether to display individual user names, or that multiple people are typing in your user interface.
### Unsubscribe from typing events
```javascript
// Initial subscription
import { TypingSetEvent } from '@ably/chat';
const { unsubscribe } = room.typing.subscribe((event: TypingSetEvent) => {
console.log('Typing event received: ', event);
});
// To remove the listener
unsubscribe();
```
```kotlin
// Initial subscription
val (unsubscribe) = room.typing.subscribe { event ->
println("Typing event received: $event")
}
// To remove the listener
unsubscribe()
```
```javascript
await room.typing.keystroke();
```
```react
import { useTyping } from '@ably/chat/react';
const MyComponent = () => {
const { keystroke, currentlyTyping, roomError } = useTyping();
const handleKeystrokeClick = () => {
keystroke();
};
return (
{roomError && Typing Error: {roomError.message}
}
Currently typing: {Array.from(currentlyTyping).join(', ')}
);
};
```
```swift
try await room.typing.keystroke()
```
```kotlin
room.typing.keystroke()
```
```jetpack
import androidx.compose.material.*
import androidx.compose.runtime.*
import com.ably.chat.Room
import kotlinx.coroutines.launch
@Composable
fun TypingKeystrokeComponent(room: Room) {
val coroutineScope = rememberCoroutineScope()
var text by remember { mutableStateOf("") }
TextField(
value = text,
onValueChange = { newText ->
text = newText
coroutineScope.launch {
room.typing.keystroke()
}
},
label = { Text("Type a message") }
)
}
```
```javascript
await room.typing.stop();
```
```react
import { useTyping } from '@ably/chat/react';
const MyComponent = () => {
const { stop, roomError } = useTyping();
const handleStopClick = () => {
stop();
};
return (
{roomError && Typing Error: {roomError.message}
}
);
};
```
```swift
try await room.typing.stop()
```
```kotlin
room.typing.stop()
```
```jetpack
import androidx.compose.material.*
import androidx.compose.runtime.*
import com.ably.chat.Room
import kotlinx.coroutines.launch
@Composable
fun StopTypingComponent(room: Room) {
val coroutineScope = rememberCoroutineScope()
Button(onClick = {
coroutineScope.launch {
room.typing.stop()
}
}) {
Text("Stop Typing")
}
}
```
### Typing Event Frequency
The Typing feature includes a configurable timer that controls how often typing events are sent to the server. This timer is reset each time a new typing event is sent, it works as follows:
* On the **first call** to `keystroke()`, the timer is set and an event is sent to the server.
* **Subsequent calls** before the timer expires result in a no-op.
* After the timer expires, a new typing event is sent and the timer is reset.
* If `stop()` is called, the timer is reset and a `typing.stopped` event is sent to the server.
You can configure the length of this timer using the `heartbeatThrottleMs` parameter in `RoomOptions` (default: **10,000ms**).
It is recommended that you call `keystroke()` with every keypress, and the SDK will handle when and if to send a typing indicator to the server.
### Emulating User Behavior
You can emulate user behavior (e.g., in chatbots) by setting a timeout to call `keystroke()` at intervals equal to the `heartbeatThrottleMs` plus a small delay, e.g. 200ms. This will ensure the typing indicator remains active.
### Grace Period for Typing Events
For the recipient of typing events:
* The typing indicator remains active for the **duration** defined by the `heartbeatThrottleMs` parameter, plus a predefined **2000ms grace period**.
* Receiving a new typing event before the grace period ends will reset the timeout.
* If the grace period ends without receiving a new typing event, the SDK will emit a `typing.stopped` event for that client to any subscribed listeners.
**For example:** With the `heartbeatThrottleMs` set to **10,000ms**, the typing indicator remains active for **12,000ms**. If no new typing event is received within this time, the SDK will emit a `typing.stopped` event.
### Adjusting the Event Frequency
You can adjust the `heartbeatThrottleMs` parameter to balance responsiveness and resource costs:
* **Increase responsiveness**: Lower the value → More typing events are sent to the server → Higher cost as more messages are sent.
* **Save resource costs**: Raise the value → Fewer typing events are sent to the server → Lower responsiveness, but less cost as fewer messages are sent overall.
This balance allows you to optimize cost and responsiveness based on your applications needs.
## Retrieve a list of users that are currently typing
```javascript
const currentlyTypingClientIds = room.typing.current;
```
```swift
let currentlyTypingClientIds = room.typing.current
```
```kotlin
val currentlyTypingClientIds = room.typing.current
```
```jetpack
val currentlyTypingClientIds = room.typing.current
```