Channel and thread hooks
These hooks read the state of one channel: its capabilities, members, name and image, slow mode, typing users, and read state. Threads have no state hooks: read thread.state with useStateStore, as Where the other behavior lives shows. The hooks that find a thread or the thread list are useThreadContext and useThreadListItemContext on State and entity hooks, and useThreadsContext on Loading data hooks.
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.
Most hooks here take the channel as an argument instead of reading it from context. They work in a channel list row as well as inside a Channel. Inside one, pass useChannel().
useChannelOwnCapabilities
Returns what the current user may do in a channel.
useChannelOwnCapabilities(channel?: Channel): string[] | undefined- Returns: the current user's capabilities in the channel, such as
send-messageorupload-file.undefinedwithout a channel. - Reads:
channel.state, fieldownCapabilities. - Re-renders: when
ownCapabilitieschanges. - Needs: nothing.
Capabilities are explained on What channels and threads hold. Inside a Channel, useOwnCapabilitiesContext() returns the same capabilities as booleans, with the overrides of the overrideOwnCapabilities prop applied: see OwnCapabilitiesContext.
import { Text } from "react-native";
import {
useChannel,
useChannelOwnCapabilities,
} from "stream-chat-react-native";
export const UploadHint = () => {
const channel = useChannel();
const capabilities = useChannelOwnCapabilities(channel);
if (!capabilities || capabilities.includes("upload-file")) return null;
return <Text>You can't upload files in this channel.</Text>;
};useCanEditChannel
Says whether the current user may change the channel's name and image.
useCanEditChannel(channel?: Channel): boolean- Returns:
truewhen the user has theupdate-channelcapability.falsewithout a channel. - Reads:
channel.state, fieldownCapabilities. - Re-renders: when
ownCapabilitieschanges. - Needs: nothing.
useCanAddMembersToChannel
Says whether the current user may add members to the channel.
useCanAddMembersToChannel(channel?: Channel): boolean- Returns:
truewhen the user has theupdate-channel-memberscapability.falsewithout a channel. - Reads:
channel.state, fieldownCapabilities. - Re-renders: when
ownCapabilitieschanges. - Needs: nothing.
useChannelMembersState
Returns the members of a channel.
useChannelMembersState(channel: Channel, pick?: ChannelMemberPick): Record<string, ChannelMemberResponse>
useChannelMembersState(channel?: Channel, pick?: ChannelMemberPick): Record<string, ChannelMemberResponse> | undefinedpick is (member) => Record<string, unknown>. It names the fields of a member the component reads.
- Returns: the loaded members, keyed by user ID.
undefinedwithout a channel. - Reads:
channel.state, fieldmembers. - Re-renders: without
pick, whenevermemberschanges. A member coming online or going offline counts as a change, because it replaces that member's object. Withpick, only when a member joins or leaves, or a picked field changes for some member. - Needs: nothing.
members holds the members the channel has loaded, which can be fewer than all of them. The total is memberCount, from useChannelMemberCount.
Define pick at module scope. A new function on each render builds a new selector, and the hook subscribes again:
import { Text } from "react-native";
import type { Channel, ChannelMemberResponse } from "stream-chat";
import { useChannelMembersState } from "stream-chat-react-native";
// Ignores presence changes, which would re-render a list of names.
const pickName = (member: ChannelMemberResponse) => ({
name: member.user?.name,
});
export const MemberNames = ({ channel }: { channel: Channel }) => {
const members = useChannelMembersState(channel, pickName);
return (
<Text>
{Object.values(members)
.map((member) => member.user?.name ?? member.user_id)
.join(", ")}
</Text>
);
};useIsChannelMember
Says whether a user is a member of a channel.
useIsChannelMember(channel: Channel, userId?: string): boolean- Returns:
truewhen one of the loaded members has that user ID.falsewithout a user ID. - Reads:
channel.state, fieldmembers. - Re-renders: whenever
memberschanges, presence changes included. - Needs: nothing.
It looks only at the loaded members, so it can return false for a member of a large channel whose member list is partly loaded.
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, which the client applies from query responses and frommember.addedandmember.updated. - Needs: nothing.
Membership is explained on What channels and threads hold. useIsChannelPinned(channel), on Channel preview hooks, reads pinned_at from it.
useChannelMemberCount
Returns the number of members of a channel.
useChannelMemberCount(channel: Channel): number
useChannelMemberCount(channel?: Channel): number | undefined- Returns: the member count the server reports, including members not loaded.
undefinedwithout a channel. - Reads:
channel.state, fieldmemberCount. - Re-renders: when
memberCountchanges. - Needs: nothing.
useChannelOnlineMemberCount
Returns the number of users watching a channel.
useChannelOnlineMemberCount(channel: Channel): number
useChannelOnlineMemberCount(channel?: Channel): number | undefined- Returns: the channel's watcher count: the users who have the channel open now.
undefinedwithout a channel. - Reads:
channel.state, fieldwatcherCount. - Re-renders: when
watcherCountchanges. - Needs: nothing.
The channel details screen and ChannelDetailsBottomSheet show it as the "online" count of a group channel.
useChannelName
Returns the name a channel was given.
useChannelName(channel?: Channel): string | undefined- Returns:
channel.data.custom.name, orundefinedwhen the channel has no name. - Reads:
channel.state, fielddata. - Re-renders: when the name changes.
- Needs: nothing.
It returns only the name set on the channel. For the name the SDK shows, which falls back to the members' names, use useChannelPreviewDisplayName on Channel preview hooks.
useChannelImage
Returns the image a channel was given.
useChannelImage(channel?: Channel): string | undefined- Returns:
channel.data.custom.image, orundefinedwhen the channel has no image. - Reads:
channel.state, fielddata. - Re-renders: when the image changes.
- Needs: nothing.
useIsDirectChat
Says whether a channel is a conversation with one other user.
useIsDirectChat(channel?: Channel): boolean- Returns:
truewhen the loaded members include exactly one user other than the current user.falsewithout a channel. - Reads:
channel.state, fieldmembers, with apickof the user ID, and the current user's ID. - Re-renders: when a member joins or leaves.
- Needs:
Chat. Throws outside it.
The SDK uses it to pick between the labels and actions of a direct chat and those of a group, such as "Delete Chat" or "Delete Group".
useChannelMuteActive
Says whether a channel shows as muted.
useChannelMuteActive(channel: Channel): boolean- Returns: in a group channel, whether the current user muted the channel. In a direct chat, whether the user muted the channel or muted the other member.
- Reads:
channel.state, fieldsmuteStatusandmembers, andclient.mutedUsersStorefor the other member. - Re-renders: when the channel's mute status changes, when the other member is muted or unmuted, and when a member joins or leaves.
- Needs:
Chat. Throws outside it.
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 of theChannelorThread. 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.
The default composer shows CooldownTimer in place of the send button while this is above 0. Slow mode is explained on What channels and threads hold and Slow mode. The Composer hooks examples use it in a send button.
useTypingString
Returns the text that says who is typing.
useTypingString(): string-
Returns:
''when nobody else is typing;- "Bob is typing" for one user, from the
channelPreview.typing.withUser.labelkey; - "Bob and 2 more are typing" for more, from the
messageList.typing.withMoreUsers.labelkey.
It uses each user's
name, or their ID. The current user is left out. -
Reads:
channel.state, fieldtyping. Inside aThread, it counts only the users typing in that thread. Inside aChannel, only those typing in the channel's main list. -
Re-renders: when
typingchanges, and when the language changes. -
Needs:
Chat, and aChannelor aThread. Throws outside them.
The SDK has no channel header: the hook is for a header of your own, and the SDK's components don't call it. Typing is explained on Typing indicators. For a channel list row, which has no Channel around it, use useChannelTypingState on Channel preview hooks.
useChannelTypingUsers
Returns the users typing in a channel.
useChannelTypingUsers(channel: Channel, threadId?: string): UserResponse[]- Returns: the users typing in the channel's main list, or, with
threadId, in that thread. The current user is left out. - Reads:
channel.state, fieldtyping. - Re-renders: when
typingchanges. - Needs:
Chat. Throws outside it.
It takes the channel as an argument, so it works in a channel list row. useChannelTypingState on Channel preview hooks is built on it.
useTypingUsers
Returns the users typing where the component sits.
useTypingUsers(): UserResponse[]- Returns:
useChannelTypingUsersfor the channel of theChannelorThread. Inside aThread, the users typing in that thread. - Reads:
channel.state, fieldtyping. - Re-renders: when
typingchanges. - Needs:
Chat, and aChannelor aThread. Throws outside them.
TypingIndicator shows these users. Use the hook for a typing indicator of your own, for example one with avatars. For the text that says who is typing, use useTypingString.
useMarkRead
Returns the function that marks a channel read the way the SDK does.
useMarkRead(channel: Channel): (options?: { updateChannelUnreadState?: boolean }) => void- Returns: a function with a stable identity. Each call:
- does nothing when the channel is pending disposal;
- when read events are off for the channel, calls
Channel.markReadLocally()if local unread counts are on, and stops; - otherwise reports the read through
client.messageDeliveryReporter.throttledMarkRead(channel), which throttles the requests and uses amarkReadRequesthandler when you registered one; - unless
updateChannelUnreadStateisfalse, clears the unread boundary ofchannel.messagePaginator, so the unread separator and the unread notification go away.
- Reads: no store. It reads the channel's read settings when it's called.
- Re-renders: never by itself.
- Needs:
Chat. Throws outside it.
Channel, MessageList and MessageFlashList mark the channel read with it. Read state and unread counts has a "Mark as read" button built on it.
useOwnUnreadCount
Returns the current user's unread count in a channel.
useOwnUnreadCount(channel: Channel | undefined): number | undefined- Returns: the
unread_messagesof the current user's read state in the channel,0when the user has none.undefinedwithout a channel. - Reads:
channel.state, fieldread. - Re-renders: when the current user's unread count changes. Reads of other members don't re-render.
- Needs:
Chat. Throws outside it.
ScrollToBottomButton shows it in its badge in a channel. It takes the channel as an argument, so it works in a channel list row. Read state is explained on Read state and unread counts.
Where the other behavior 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. Set its filters, sort and page size there. 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 current user's count is useOwnUnreadCount, above. |
| Watchers | channel.state, watchers and watcherCount |
| Other channel data | channel.state, data |
| Whether the channel has loaded and is watched | channel.state, initialized and watchStatus |
| The latest message | channel.messagePaginator.aggregateState, latestMessage |
| Delivery and read receipts | channel.messageReceiptsTracker.snapshotStore. The receipt hooks are on Message hooks. |
| A thread's parent message, reply count and read state | thread.state, parentMessage, replyCount and read |
| A thread's replies | thread.messagePaginator.state, items |
| 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 state, aiState, has its own hook on AI hooks.
A thread's unread count for the current user:
import { Text } from "react-native";
import type { ThreadState } from "stream-chat";
import {
useChatContext,
useStateStore,
useThreadContext,
} from "stream-chat-react-native";
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 <Text>{unread}</Text>;
};