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

Channel and thread hooks

These hooks read the state of one channel: its capabilities, members, display name, slow mode and read receipts. Threads have no hooks of their own: read thread.state with useStateStore, as Where the other behaviour lives shows.

What a channel and a thread hold is on What channels and threads hold, and read state on Read state and unread counts. How to read state in components is on Reading state in components.

Each entry gives the signature, then:

  • Returns: what the hook gives back.
  • Reads: the store or the context it reads.
  • Re-renders: what makes the component render again.
  • Needs: where the component must sit, and what the hook does outside that place.

useChannelCapabilities

Returns what the current user may do in the channel the component renders in.

useChannelCapabilities(params: { cid: string | undefined }): Set<string>
  • Returns: the current user's capabilities in the channel from useChannel(), such as send-message or upload-file. An empty set when cid is undefined or isn't that channel's cid.
  • Reads: channel.state, field ownCapabilities.
  • Re-renders: when ownCapabilities changes.
  • Needs: a Channel or a Thread. Throws outside both.

It only answers for the channel the component renders in. For another channel, such as a row in a channel list, select ownCapabilities from that channel's state with useStateStore. Capabilities are explained on What channels and threads hold.

import { useChannel, useChannelCapabilities } from "stream-chat-react";

export const UploadHint = () => {
  const channel = useChannel();
  const capabilities = useChannelCapabilities({ cid: channel.cid });

  if (capabilities.has("upload-file")) return null;
  return <span>You can't upload files in this channel.</span>;
};

useChannelMembersState

Returns the members of a channel.

useChannelMembersState(channel: Channel): Record<string, ChannelMemberResponse>
useChannelMembersState(channel?: Channel): Record<string, ChannelMemberResponse> | undefined
  • Returns: the loaded members, keyed by user ID. undefined without a channel.
  • Reads: channel.state, field members.
  • Re-renders: when members changes.
  • Needs: nothing. It doesn't read the channel from context, so pass one.

members holds the members the channel has loaded, which can be fewer than all of them. The total is memberCount in channel.state.

useChannelMembershipState

Returns the current user's membership in a channel.

useChannelMembershipState(channel: Channel): ChannelMemberResponse
useChannelMembershipState(channel?: Channel): ChannelMemberResponse | undefined
  • Returns: the current user's member record: its channel_role, and pinned_at and archived_at when the user pinned or archived the channel. undefined without a channel.
  • Reads: channel.state, field membership.
  • Re-renders: when membership changes. Pinning and archiving change it when the member.updated event arrives, not when the call is made.
  • Needs: nothing. It doesn't read the channel from context, so pass one.

A badge for a pinned channel in a channel list row:

import type { Channel } from "stream-chat";
import { useChannelMembershipState } from "stream-chat-react";

export const PinnedBadge = ({ channel }: { channel: Channel }) => {
  const membership = useChannelMembershipState(channel);
  return membership.pinned_at ? <span>Pinned</span> : null;
};

Membership is explained on What channels and threads hold.

useChannelDisplayName

Returns the name to show for a channel.

useChannelDisplayName(channel: Channel | undefined): string | undefined
  • Returns, the first that applies:
    1. the channel's name, from channel.data.custom.name;
    2. in a channel with two members, the other member's name, or the translated "Direct message" when that user has no name;
    3. in a channel with more members, the names of two other members, joined with a comma;
    4. undefined.
  • Reads: channel.state, fields data and members, and the current user's ID.
  • Re-renders: when the channel's data or members change.
  • Needs: Chat. Throws outside it.

useChannelPreviewInfo

Returns the title, image and group members a channel preview shows.

useChannelPreviewInfo(params: { channel?: Channel; overrideImage?: string; overrideTitle?: string }): { displayImage: string | undefined; displayTitle: string | undefined; groupChannelDisplayInfo: GroupChannelDisplayInfo }
  • Returns:
    • displayTitle: overrideTitle, or else the name from useChannelDisplayName.
    • displayImage: overrideImage, or else channel.data.custom.image, or else in a channel with two members the other member's image.
    • groupChannelDisplayInfo: in a channel with more than two members, { members } with the name and image of each member that has one. { members: [] } otherwise.
  • Reads: channel.state, fields data and members, and the current user's ID.
  • Re-renders: when the channel's data or members change.
  • Needs: Chat. Throws outside it. Without a channel, the title and image are the overrides you pass, or undefined.
import type { Channel } from "stream-chat";
import { useChannelPreviewInfo } from "stream-chat-react";

export const ChannelTitle = ({ channel }: { channel: Channel }) => {
  const { displayImage, displayTitle } = useChannelPreviewInfo({ channel });

  return (
    <div className="channel-title">
      {displayImage && <img alt="" src={displayImage} />}
      <span>{displayTitle ?? "Unnamed channel"}</span>
    </div>
  );
};

useCooldownRemaining

Returns the seconds left before the current user can send again in slow mode.

useCooldownRemaining(): number
  • Returns: the seconds left, rounded. 0 when slow mode is off, when the user can skip it, or when the wait is over.
  • Reads: channel.cooldownTimer.state, field cooldownRemaining, of the channel from useChannel(). Inside a Thread, that's the thread's channel: slow mode applies to the whole channel.
  • Re-renders: every second while the user waits, and when slow mode changes.
  • Needs: a Channel or a Thread. Throws outside both.

Slow mode is explained on What channels and threads hold and Slow mode. The Composer hooks examples use it in a send button.

useIsCooldownActive

Returns whether the current user is waiting to send again in slow mode.

useIsCooldownActive(): boolean
  • Returns: true while the seconds left are above 0, false otherwise.
  • Reads: channel.cooldownTimer.state, field cooldownRemaining, of the channel from useChannel(). Inside a Thread, that's the thread's channel.
  • Re-renders: when the wait starts and when it ends. Unlike useCooldownRemaining, not every second.
  • Needs: a Channel or a Thread. Throws outside both.

The composer uses it while the user waits: MessageComposerActions shows CooldownTimer in place of the send button, the emoji picker isn't rendered, AttachmentSelector is disabled, and the drop zone of WithDragAndDropUpload is turned off.

useLastReadData

Returns who has read each message of a rendered list.

useLastReadData(params: { channel: Channel; messages: LocalMessage[]; returnAllReadData: boolean; lastOwnMessage?: LocalMessage }): Record<string, UserResponse[]>
  • Returns: a map from message ID to the users whose read state is at or past that message.
    • messages are the messages you render, oldest first.
    • With returnAllReadData: true, the map has an entry for every message in messages.
    • With returnAllReadData: false, it has one entry, for lastOwnMessage, and is empty without it.
    • The lists can include the current user. Leave them out for a "seen by" label.
  • Reads: channel.messageReceiptsTracker.snapshotStore.
  • Re-renders: when the tracker publishes new read or delivery state.
  • Needs: nothing. Pass the channel.

MessageList and VirtualizedMessageList call it with their returnAllReadData prop. The tracker is on Message receipts tracker, and the feature on Read state, unread and receipts.

Where the other behaviour lives

Querying, paging and reconnecting have no hooks. They belong to the state layer, so they work whether or not a component is mounted:

  • Querying and pagination belong to the ChannelPaginator you pass to ChannelList. Configure its filters, sort and page size there rather than wrapping the list. See Channel list paginators.
  • Reconnect recovery belongs to the client. After a reconnect it refreshes the loaded channel lists and reloads the open channels and threads, then dispatches connection.recovered. You don't need to listen for that event to refresh a list. See Connection recovery.
  • Which lists exist belongs to client.channelManager, which holds the paginators and decides which list owns a channel that matches several.

For the rest of a channel's or a thread's state, use useStateStore with your own selector:

To read Store and field
Read state and the unread count channel.state, read. The UnreadBadge example computes the count.
Typing users channel.state, typing
Watchers channel.state, watchers and watcherCount
Name, image and other channel data channel.state, data
The member count channel.state, memberCount
Whether the channel is muted channel.state, muteStatus
Whether the channel has loaded and is watched channel.state, initialized and watchStatus
The latest message channel.messagePaginator.aggregateState, latestMessage
A thread's parent message, replies and read state thread.state, parentMessage, replyCount and read
The unread thread count client.threads.state, unreadThreadCount

The fields of these stores are on Channel and channel state and Thread and thread manager. The AI indicator state, aiState, has its own hook on AI hooks.

A thread's unread count for the current user:

import type { ThreadState } from "stream-chat";
import {
  useChatContext,
  useStateStore,
  useThreadContext,
} from "stream-chat-react";

const readSelector = (state: ThreadState) => ({ read: state.read });

export const ThreadUnreadCount = () => {
  const { client } = useChatContext();
  const thread = useThreadContext();
  const { read } = useStateStore(thread?.state, readSelector) ?? {};

  const unread = client.userID
    ? (read?.[client.userID]?.unreadMessageCount ?? 0)
    : 0;
  if (!unread) return null;

  return <span className="unread-badge">{unread}</span>;
};