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 assend-messageorupload-file. An empty set whencidisundefinedor isn't that channel'scid. - Reads:
channel.state, fieldownCapabilities. - Re-renders: when
ownCapabilitieschanges. - Needs: a
Channelor aThread. 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.
undefinedwithout a channel. - Reads:
channel.state, fieldmembers. - Re-renders: when
memberschanges. - 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, andpinned_atandarchived_atwhen the user pinned or archived the channel.undefinedwithout a channel. - Reads:
channel.state, fieldmembership. - Re-renders: when
membershipchanges. Pinning and archiving change it when themember.updatedevent 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:
- the channel's
name, fromchannel.data.custom.name; - in a channel with two members, the other member's name, or the translated "Direct message" when that user has no name;
- in a channel with more members, the names of two other members, joined with a comma;
undefined.
- the channel's
- Reads:
channel.state, fieldsdataandmembers, and the current user's ID. - Re-renders: when the channel's
dataormemberschange. - 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 fromuseChannelDisplayName.displayImage:overrideImage, or elsechannel.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, fieldsdataandmembers, and the current user's ID. - Re-renders: when the channel's
dataormemberschange. - Needs:
Chat. Throws outside it. Without a channel, the title and image are the overrides you pass, orundefined.
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.
0when slow mode is off, when the user can skip it, or when the wait is over. - Reads:
channel.cooldownTimer.state, fieldcooldownRemaining, of the channel fromuseChannel(). Inside aThread, 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
Channelor aThread. 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:
truewhile the seconds left are above0,falseotherwise. - Reads:
channel.cooldownTimer.state, fieldcooldownRemaining, of the channel fromuseChannel(). Inside aThread, that's the thread's channel. - Re-renders: when the wait starts and when it ends. Unlike
useCooldownRemaining, not every second. - Needs: a
Channelor aThread. 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.
messagesare the messages you render, oldest first.- With
returnAllReadData: true, the map has an entry for every message inmessages. - With
returnAllReadData: false, it has one entry, forlastOwnMessage, 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
ChannelPaginatoryou pass toChannelList. 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>;
};