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 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-message or upload-file. undefined without a channel.
  • Reads: channel.state, field ownCapabilities.
  • Re-renders: when ownCapabilities changes.
  • 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: true when the user has the update-channel capability. false without a channel.
  • Reads: channel.state, field ownCapabilities.
  • Re-renders: when ownCapabilities changes.
  • Needs: nothing.

useCanAddMembersToChannel

Says whether the current user may add members to the channel.

useCanAddMembersToChannel(channel?: Channel): boolean
  • Returns: true when the user has the update-channel-members capability. false without a channel.
  • Reads: channel.state, field ownCapabilities.
  • Re-renders: when ownCapabilities changes.
  • 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> | undefined

pick is (member) => Record<string, unknown>. It names the fields of a member the component reads.

  • Returns: the loaded members, keyed by user ID. undefined without a channel.
  • Reads: channel.state, field members.
  • Re-renders: without pick, whenever members changes. A member coming online or going offline counts as a change, because it replaces that member's object. With pick, 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: true when one of the loaded members has that user ID. false without a user ID.
  • Reads: channel.state, field members.
  • Re-renders: whenever members changes, 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, 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, which the client applies from query responses and from member.added and member.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. undefined without a channel.
  • Reads: channel.state, field memberCount.
  • Re-renders: when memberCount changes.
  • 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. undefined without a channel.
  • Reads: channel.state, field watcherCount.
  • Re-renders: when watcherCount changes.
  • 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, or undefined when the channel has no name.
  • Reads: channel.state, field data.
  • 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, or undefined when the channel has no image.
  • Reads: channel.state, field data.
  • 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: true when the loaded members include exactly one user other than the current user. false without a channel.
  • Reads: channel.state, field members, with a pick of 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, fields muteStatus and members, and client.mutedUsersStore for 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. 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 of the Channel or Thread. 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.

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.label key;
    • "Bob and 2 more are typing" for more, from the messageList.typing.withMoreUsers.label key.

    It uses each user's name, or their ID. The current user is left out.

  • Reads: channel.state, field typing. Inside a Thread, it counts only the users typing in that thread. Inside a Channel, only those typing in the channel's main list.

  • Re-renders: when typing changes, and when the language changes.

  • Needs: Chat, and a Channel or a Thread. 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, field typing.
  • Re-renders: when typing changes.
  • 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: useChannelTypingUsers for the channel of the Channel or Thread. Inside a Thread, the users typing in that thread.
  • Reads: channel.state, field typing.
  • Re-renders: when typing changes.
  • Needs: Chat, and a Channel or a Thread. 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:
    1. does nothing when the channel is pending disposal;
    2. when read events are off for the channel, calls Channel.markReadLocally() if local unread counts are on, and stops;
    3. otherwise reports the read through client.messageDeliveryReporter.throttledMarkRead(channel), which throttles the requests and uses a markReadRequest handler when you registered one;
    4. unless updateChannelUnreadState is false, clears the unread boundary of channel.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_messages of the current user's read state in the channel, 0 when the user has none. undefined without a channel.
  • Reads: channel.state, field read.
  • 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 ChannelPaginator you pass to ChannelList. 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>;
};