Channel and channel state
A Channel is the client's object for one channel. Its data, members, read state and typing users are in the store channel.state. Its messages, pinned messages, composer and message operations are objects it owns. This page lists the API of Channel and ChannelState in stream-chat.
What a channel holds, and why, is explained on What channels and threads hold. Getting and loading a channel is explained on Shared entities.
Getting a channel
The client keeps one Channel per cid. Get it from the client instead of constructing it with new Channel():
| Call | Returns |
|---|---|
client.channel(type, id, data) |
The stored channel for type:id, or a new one that the client stores from then on. Sends no request. id and data are optional. client.channel(type, { members }) creates a channel from its members, without an id. |
client.channelManager.get(cid) |
The stored channel, or undefined. A stored channel can be one that was disposed. |
Channel.watch(options) |
Watches the channel unless it's watched and loaded already, and resolves with the instance to use from then on. options is optional. |
A few rules apply to client.channel():
- It throws without a user. Call it after
client.connectUser(), which doesn't have to finish first. - For a channel the client already stores, it applies only
data.customand ignores the rest ofdata. - A stored channel that was disposed is replaced by a new instance.
- Without an
id,data.memberscreates a distinct channel between those members, or returns a loaded distinct channel with the same members. A new one is provisional until its first query answers. A channel with neither isn't stored.
This opens "general" and watches it, whether or not another part of the app is opening it at the same time:
const general = await client.channel("messaging", "general").watch();A channel identifies itself with these properties:
| Property | What it holds |
|---|---|
cid |
type:id. A channel created from its members has a temporary cid until the server assigns its id. |
id |
The channel id, or undefined while the channel is provisional. |
type |
The channel type, such as messaging. |
isProvisional |
true while the channel has no id. Not in a store: subscribe to initialized, which the same response sets. |
The objects a channel owns
| Property | What it is |
|---|---|
channel.state |
The ChannelState store described below. |
channel.messagePaginator |
The channel's message list. See Message paginator. |
channel.pinnedMessagesPaginator |
The channel's pinned messages. See Pinned messages paginator. |
channel.messageComposer |
The composer for new messages. See MessageComposer API. |
channel.messageOperations |
Sends, retries, edits and deletes messages, and adds reactions. See Message operations. |
channel.messageReceiptsTracker |
Who read and who received each message. See Message receipts tracker. |
channel.cooldownTimer |
The slow mode countdown for the current user. See Slow mode. |
A thread has its own message list, composer and message operations. Inside a thread, use the thread's, not the channel's.
channel.state fields
channel.state is a ChannelState, a store of ChannelStateData. Every field below is in that one store, so a subscription selects the fields it needs:
| Field | Type | What it holds |
|---|---|---|
data |
Partial<ChannelResponse> or undefined |
The channel's server data: frozen, hidden, cooldown, member_count, own_capabilities and the rest, and custom, which holds name, image and your own fields. Before the first load, the data passed to client.channel(). |
members |
Record<string, ChannelMemberResponse> |
The loaded members, by user id. A large channel loads only some of them. |
memberCount |
number |
The total member count, from data.member_count. |
membership |
ChannelMemberResponse |
The current user's own membership: channel_role, archived_at, pinned_at and the rest. An empty object until it's loaded. |
watchers |
Record<string, UserResponse> |
The users watching the channel now, by user id. |
watcherCount |
number |
How many users are watching. |
watchStatus |
ChannelWatchStatus |
Whether this client watches the channel: "watching", "wasWatching" (the connection dropped) or "notWatching". |
read |
Record<string, ...> |
Each member's read state, by user id. The fields are in the next table. |
typing |
Record<string, Event> |
Who is typing, by user id: each user's latest typing.start event. This is the only place typing users are kept, and the SDK's typing indicators read it. |
ownCapabilities |
string[] |
What the current user may do here, such as send-message or upload-file. |
muteStatus |
ChannelMuteStatus |
{ muted, createdAt, expiresAt }. The dates are in nanoseconds, or null. |
initialized |
boolean |
A live server response was applied to this instance. Data from the offline database doesn't count. |
offlineMode |
boolean |
The data came from the offline database, not from the server. A query that answers sets it back to false. |
active |
boolean |
Something has activated the channel with Channel.activate() and not released it yet. |
pendingDisposal |
boolean |
The instance was disposed and can't be used any more. |
supersededBy |
Channel or undefined |
The instance that replaced this one. See Superseded channels. |
aiState |
AIState |
The state of an AI response: one of the AIStates values, AIStates.Idle by default. |
With offline support, a channel loaded from the local database has offlineMode: true and initialized: false until the server answers. Check both to tell whether a channel has any data: initialized || offlineMode.
Each entry of read has these fields. The timestamps are in nanoseconds:
| Field | What it holds |
|---|---|
user |
The user. |
last_read |
When the user last read the channel. |
unread_messages |
How many messages the user hasn't read. |
last_read_message_id |
The last message the user read, when the server knows it. |
first_unread_message_id |
The message the user marked unread from, after a mark unread. |
last_delivered_at |
When a message was last delivered to the user. |
last_delivered_message_id |
The last message delivered to the user. |
Read state and unread counts explains which events change it.
This handler runs whenever the member count or the current user's unread count changes:
const ownUserId = client.userId;
const unsubscribe = channel.state.subscribeWithSelector(
({ memberCount, read }) => ({
memberCount,
unread: ownUserId ? (read[ownUserId]?.unread_messages ?? 0) : 0,
}),
({ memberCount, unread }) => {
console.log(`${memberCount} members, ${unread} unread`);
},
);In a component, read the same fields with useStateStore(channel.state, selector) from stream-chat-react-native, with the selector defined outside the component.
Getters
These getters return the value at the moment you read them, and don't tell you when it changes. To follow a change, subscribe to channel.state.
| Getter | What it reads |
|---|---|
channel.data |
data |
channel.initialized, channel.offlineMode |
initialized, offlineMode |
channel.watchStatus, channel.active |
watchStatus, active |
channel.pendingDisposal, channel.supersededBy |
pendingDisposal, supersededBy |
channel.state.members, channel.state.membership |
members, membership |
channel.state.read, channel.state.watchers |
read, watchers |
channel.state.member_count, channel.state.watcher_count |
memberCount, watcherCount |
channel.state.typing |
typing |
channel.state.unreadCount |
The current user's unread_messages, or 0 |
channel.state.pending_messages holds the channel's pending messages from the last query. It isn't in the store.
Some of these have setters. They exist for the SDK: app code changes a channel through the methods below, not by writing its state. Editing a field of channel.data in place doesn't reach the store at all.
Configuration
A channel has two views of its settings:
channel.configis the configuration in effect: the channel type's server settings combined with what the app registered inclient.config.channel.configStateis the same value as a store.channel.serverConfigis the channel type's settings as the server sent them (ChannelConfigWithInfo):typing_events,read_events,replies,uploads,url_enrichment,polls,max_message_length,commandsand the rest. It'sundefineduntil the channel has been queried or watched.
Check channel.config to decide whether a feature is available. Either side can turn a feature off, and only config includes both. channel.config has these fields:
| Field | Default | Combined with the server's |
|---|---|---|
typingEvents.enabled |
true |
typing_events |
readEvents.enabled |
true |
read_events |
readEvents.localUnreadCountEnabled |
false |
Nothing. Counts unread messages locally while read events are off. |
replies.enabled |
true |
replies |
userMessageReminders.enabled |
true |
user_message_reminders |
deliveryEvents.enabled |
true |
delivery_events |
availableCommands |
[] |
commands, which it copies. The app can't set it. |
requestHandlers |
none | Nothing. Replaces the requests for send, retry, update, delete and mark read. |
Change these through client.config, under the channel key. A channel has no updateConfig():
client.config.set({
channel: {
readEvents: { enabled: false, localUnreadCountEnabled: true },
typingEvents: { enabled: false },
},
});The channel key also holds messagePaginator, pinnedMessagesPaginator and messageOperations, which go to the objects the channel owns. Configuration explains how settings resolve, and Configuration tree lists every key.
Loading and lifecycle methods
| Method | Returns | What it does |
|---|---|---|
watch(options, requestOptions) |
Promise<Channel> |
Loads the channel, its members, read state and latest messages, and watches it. Sends nothing for a channel already watched and loaded, unless the options differ from the current watch's. A call made while the same watch is waiting joins it. Resolves with this instance or the one that superseded it. Both arguments are optional. |
query(options, requestOptions) |
Promise<ChannelStateResponse> |
Loads the channel with the options you pass. Watches it only with watch: true. The messages it returns aren't added to the message list. |
create(options) |
Promise<ChannelStateResponse> |
Creates the channel on the server, or gets it, without watching it. Asks for no messages, members or read state. |
reload() |
Promise<void> |
Watches again and refreshes the newest loaded messages, keeping messages that failed to send, including those kept in the offline database. Use it to refresh a watched channel, because repeating watch() doesn't. Does nothing for a channel that was never loaded from the server or the offline database. |
stopWatching(request, requestOptions) |
Promise<Response> |
Ends the watch. watchStatus becomes "notWatching", and a reconnect doesn't restore it. |
activate() |
() => void |
Marks the channel active and returns the function that releases it. Activations are counted. |
disconnect() |
void |
Disposes the instance: pendingDisposal becomes true. Sends no request, so call stopWatching() first to end the watch. |
getClient() |
StreamChat |
The client. Throws once the channel is disposed. |
options for Channel.watch(), Channel.query() and Channel.create() is a ChannelGetOrCreateRequest: messages, members and watchers pagination, presence, state, watch, data and a few more. Channel.watch() and any query with watch: true wait for the WebSocket to connect. Shared entities explains loading, and Entity lifecycle explains activation and disposal.
The Channel component does this for the channel it renders, and activates the channel until it unmounts. When it mounts, it sets messagePaginator.pageSize to 25 unless a page size is registered in client.config, then calls Channel.watch() unless the channel is initialized, its message list shows the newest messages, and its watchStatus isn't "notWatching". A "wasWatching" channel is left to the connection recovery. With initializeOnMount={false} it doesn't watch. When the watch fails, it sets offlineMode to true. See Channel.
Outside Channel, for example on a channel details screen of your own, this keeps a channel loaded while the screen shows it:
const openScreen = async (id: string) => {
const opened = await client.channel("messaging", id).watch();
// Until release() is called, reconnects reload the channel.
const release = opened.activate();
return { channel: opened, close: release };
};Channel actions
Most methods below send a request. Unless the table says otherwise, the change shows when the server's event arrives, not when the call returns. Most of them take an optional requestOptions as their last argument, which carries an abort signal. Channel.banUser(), Channel.unbanUser(), Channel.markReadViaReporter() and Channel.sendSharedLocation() don't. What's optimistic and what isn't lists how each action settles.
The current user's membership
| Method | What it does |
|---|---|
archive(), unarchive() |
Sets or clears membership.archived_at, on member.updated. |
pin(), unpin() |
Sets or clears membership.pinned_at, on member.updated. |
mute({ expiration }) |
Mutes the channel, for expiration milliseconds if given. muteStatus changes on notification.channel_mutes_updated. |
unmute() |
Unmutes the channel, on the same event. |
muteStatus() |
Returns the current ChannelMuteStatus, without a request. Throws for a provisional channel. |
hide({ clear_history }), show() |
Hides the channel from the user's channel lists until a new message arrives, or shows it again. data.hidden changes on channel.hidden and channel.visible. |
Members
Channel.addMembers(), Channel.removeMembers() and Channel.inviteMembers() each take one request object. Apart from the members, it can hold message, a system message to send with the change, and any other field of Channel.update(), such as hide_history:
| Method | What it does |
|---|---|
addMembers({ members, message, ...options }) |
Adds members by user id or as ChannelMemberRequest objects. members changes on member.added. |
removeMembers({ members, message, ...options }) |
Removes members by user id. members changes on member.removed. |
inviteMembers({ members, message, ...options }) |
Invites users. |
acceptInvite(options), rejectInvite(options) |
Accepts or rejects the current user's invitation. options is optional. |
banUser(userId, options), unbanUser(userId, options) |
Bans or unbans a user in this channel. banUser() needs options, even if it's {}. |
queryMembers({ payload }) |
A one-off query of members. Changes no state. |
This adds two members without showing them the history from before they joined:
await channel.addMembers({ members: ["bob", "cara"], hide_history: true });Channel data
| Method | What it does |
|---|---|
update(request) |
Replaces the channel's data, for example { data: { custom: { name } } }. Applies the response to data at once. |
updatePartial({ set, unset }) |
Changes some fields, for example { set: { name } }. Applies the response to data at once, and dispatches capabilities.changed when own_capabilities changed. |
enableSlowMode(seconds), disableSlowMode() |
Sets data.cooldown, through update(). |
addFilterTags({ tags }), removeFilterTags({ tags }) |
Changes the channel's filter tags, through update(). The request can also hold message and other update() fields. |
truncate(request) |
Removes the channel's messages, or those before truncated_at. The lists change on channel.truncated. |
delete({ hard_delete }) |
Deletes the channel. On channel.deleted, the client disposes the instance. |
Read state
| Method | What it does |
|---|---|
markRead(request) |
Sends the mark read request directly. Throws while the channel has neither initialized nor offlineMode, or when config.readEvents.enabled is false. read changes on message.read. |
markReadViaReporter(request) |
Marks the channel read through client.messageDeliveryReporter. Uses requestHandlers.markReadRequest when registered, and markReadLocally() while read events are off and localUnreadCountEnabled is on. |
markReadLocally() |
Resets the current user's unread count without a request, by dispatching message.read_locally. Returns the event, or undefined without a user. With offline support, the reset is stored when the channel counts unread messages locally. |
markUnread(request) |
Marks the channel unread from request.message_id or request.message_timestamp. Throws like markRead(). read changes on notification.mark_unread. |
countUnread(lastRead) |
Counts the newest loaded messages after lastRead that count as unread. Without lastRead, returns state.unreadCount. |
countUnreadMentions() |
Counts the newest loaded unread messages that mention the current user. |
lastRead() |
The current user's last_read, null if never read, or undefined without a user. |
Typing, events and AI
| Method | What it does |
|---|---|
keystroke(parentId) |
Sends typing.start, at most every 2 seconds. Pass a parent message id for a thread. Does nothing while typing events are off, while the connection is unhealthy, or when the user's privacy settings turn typing indicators off. |
stopTyping(parentId) |
Sends typing.stop, under the same conditions. |
sendEvent({ event }) |
Sends a custom event to the channel. |
updateAIState(messageId, state, options) |
Sends ai_indicator.update for a message. Usually called by the server that runs the AI. |
clearAIIndicator(), stopAIResponse() |
Send ai_indicator.clear and ai_indicator.stop. |
The message composer calls Channel.keystroke() as the user types while its text.publishTypingEvents setting is on. Channel.stopTyping() runs before each send, and about a second after the last keystroke. An app that uses the SDK's composer doesn't call them.
Requests that change no state
These send the request and return the response. Nothing in the channel's state changes until an event arrives:
Channel.sendMessage(request),Channel.sendReaction(...)andChannel.deleteReaction(...). Usechannel.messageOperations, which also updates the message list and, with offline support, queues the request while the device is offline.Channel.getPinnedMessages(request),Channel.getMessagesById(ids),Channel.queryChannelBans(request)andChannel.getDraft(request).Channel.createDraft(request)andChannel.deleteDraft(request). With offline support, they're queued as pending tasks when the device is offline. The message composer calls them when drafts are on.Channel.uploadFile(request),Channel.uploadImage(request),Channel.deleteFile({ url })andChannel.deleteImage({ url }).Channel.sendAction(messageId, formData), which runs a message action such as the buttons of a Giphy preview.Channel.vote(...)andChannel.removeVote(...)for polls, andChannel.sendSharedLocation(location)andChannel.stopLiveLocationSharing(location)for locations. With offline support,Channel.sendSharedLocation()with amessage_idis queued like a message.
Events
Channel.on(type, handler) and Channel.on(handler) subscribe to the channel's events, and return { unsubscribe }. Channel.off(type, handler) removes a handler. The client applies an event to the channel's state before any handler runs, so a handler reads the updated state. A disposed channel's state no longer changes, but its handlers still run.
const { unsubscribe } = channel.on("member.added", (event) => {
// channel.state already holds the new member
console.log(`${event.member?.user?.name} joined`, channel.state.members);
});The channel changes its state on these events:
| Event | What changes |
|---|---|
message.new |
The message list and the pinned list. Each unread_messages in read grows, except the sender's, which is reset to 0. The current user's grows only for a message that counts as unread and only while the message list isn't showing the newest messages. The current user's own message changes no count. A hidden channel becomes visible. A thread-only reply changes neither list nor count. |
message.updated, message.undeleted, message.deleted, user.messages.deleted |
The message list and the pinned list. message.updated and message.undeleted skip replies that are only in their thread. |
reaction.new, reaction.updated, reaction.deleted |
The message, in every list that shows it. |
message.read, notification.mark_read, message.read_locally |
The reader's entry in read. A thread read changes nothing here. |
notification.mark_unread |
The current user's entry in read, and the message list's unread snapshot. |
message.delivered |
The delivery fields of the user's entry in read. |
typing.start, typing.stop |
typing. An entry older than 7 seconds is removed, and the client dispatches typing.stop for it. |
member.added, member.updated |
members, and membership for the current user. |
member.removed |
members. |
user.banned, user.unbanned |
The member's banned and shadow_banned. |
user.watching.start, user.watching.stop |
watchers. |
channel.updated |
data, memberCount and ownCapabilities. When frozen changes, the channel queries itself again to refresh ownCapabilities. |
channel.truncated |
data, the message list, the pinned list and the current user's unread count. |
channel.hidden, channel.visible |
data.hidden. With clear_history, channel.hidden also empties the message list and the pinned list. |
ai_indicator.update, ai_indicator.clear, ai_indicator.stop |
aiState. |
Any event with watcher_count |
watcherCount. |
These are handled by the client, for every channel it stores:
| Event | What changes |
|---|---|
notification.channel_mutes_updated, health.check, connection.ok |
muteStatus. |
notification.mark_read with unread_channels: 0 |
The current user's unread count, in every channel. |
user.updated, user.presence.changed |
The user's object in members, membership and watchers, and in read for user.updated. |
user.deleted that deletes the user's messages, user.messages.deleted without a cid |
That user's messages in the message list and the pinned list. |
channel.deleted, notification.channel_deleted, notification.removed_from_channel |
The channel is disposed: pendingDisposal becomes true. |
The client also dispatches events of its own. capabilities.changed fires when a query or Channel.updatePartial() returns different capabilities, and channels.queried fires after every query.
user.watching.start, user.watching.stop, user.banned and user.unbanned change watchers and members in place, without a new object. So does a query that loads watchers. A subscription that selects watchers or members misses the change until another update replaces the object. member.removed doesn't clear membership when the current user is removed.
See also
- What channels and threads hold explains the channel's state.
- Shared entities and Entity lifecycle explain getting, loading and releasing channels.
- Thread and thread manager lists the API of threads.