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

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.custom and ignores the rest of data.
  • A stored channel that was disposed is replaced by a new instance.
  • Without an id, data.members creates 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.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. 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(...) and Channel.deleteReaction(...). Use channel.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) and Channel.getDraft(request).
  • Channel.createDraft(request) and Channel.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 }) and Channel.deleteImage({ url }).
  • Channel.sendAction(messageId, formData), which runs a message action such as the buttons of a Giphy preview.
  • Channel.vote(...) and Channel.removeVote(...) for polls, and Channel.sendSharedLocation(location) and Channel.stopLiveLocationSharing(location) for locations. With offline support, Channel.sendSharedLocation() with a message_id is 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.

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