Info:
This is release candidate documentation for Stream Chat React SDK v15. For the latest stable version, see the latest version (v14).

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, custom) The stored channel for type:id, or a new one that the client stores from then on. Sends no request. id and custom 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 and loads the channel unless that's done 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 the custom field of its last argument and ignores the rest.
  • A stored channel that was disposed is replaced by a new instance.
  • Without an id, members creates a distinct channel between those members. It's 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: name, image, frozen, hidden, cooldown, member_count, own_capabilities, custom and the rest. Before the first load, the data passed to client.channelManager.ensure().
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.
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.
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.

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`);
  },
);

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 The typing users, as the channel's composer holds them
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.

Configuration

A channel has two views of its settings:

  • channel.config is the configuration in effect: the channel type's server settings combined with what the app registered in client.config. channel.configState is the same value as a store.
  • channel.serverConfig is the channel type's settings as the server sent them (ChannelConfigWithInfo): typing_events, read_events, replies, uploads, url_enrichment, polls, max_message_length, commands and the rest. It's undefined until 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. Safe to call again, even from several places at once: it sends a request only when the channel isn't loaded yet, or when options differ from those it was watched with. Resolves with this instance or the one that superseded it. Both arguments are optional.
query(options) 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. Use it to refresh a watched channel: repeating watch() doesn't. Does nothing for a channel that was never loaded.
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.

This keeps a channel loaded while a 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

Method What it does
addMembers({ members, ...options }) Adds members by user id or as ChannelMemberRequest objects. members changes on member.added.
removeMembers({ members, ...options }) Removes members. members changes on member.removed.
inviteMembers({ members, ...options }) Invites users.
acceptInvite(options), rejectInvite(options) Accepts or rejects the current user's invitation.
banUser(userId, options), unbanUser(userId, options) Bans or unbans a user in this channel.
queryMembers({ payload }) A one-off query of members. Changes no state.

message and options are optional, except that Channel.banUser() needs options, even if it's {}. message is a system message to send with the change.

Channel data

Method What it does
update(request) Replaces the channel's data, for example { data: { name } }. Applies the response to data at once.
updatePartial({ set, unset }) Changes some fields. 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().
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 before the channel is loaded, 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.
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.
stopTyping(parentId) Sends typing.stop.
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.

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(...) and Channel.deleteReaction(...). Use channel.messageOperations, which also updates the message list.
  • Channel.getPinnedMessages(request), Channel.getMessagesById(ids), Channel.queryChannelBans(request) and Channel.getDraft(request).
  • Channel.uploadFile(request), Channel.uploadImage(request), Channel.deleteFile({ url }) and Channel.deleteImage({ url }).
  • Channel.vote(...) and Channel.removeVote(...) for polls, and Channel.sendSharedLocation(location) and Channel.stopLiveLocationSharing(location) for locations.

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 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.
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.

Note:

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