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

Channel preview hooks

These hooks compute what a channel list row shows: the title, the latest message, the draft, typing, presence, and the mute, pin and unread state. ChannelList uses them for its rows, and they're exported for rows of your own. They're exported from stream-chat-react-native and stream-chat-expo.

A row renders outside any Channel, so every hook here takes the channel as an argument. How the default row is built is on Channel list UI, and the row components on Channel preview.

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.

useChannelPreviewData

Returns the latest message and the mute, pin and unread state of a channel.

useChannelPreviewData(channel: Channel, client: StreamChat): {
  lastMessage: LocalMessage | MessageResponse | undefined;
  muted: boolean;
  pinned: boolean;
  unread: number;
}
  • Returns:
    • lastMessage: the channel's latest message, also when the list of the channel's messages shows an older page.
    • muted: whether the current user muted the channel.
    • pinned: whether the current user pinned it, from membership.pinned_at.
    • unread: the current user's unread count. 0 while the channel is muted.
  • Reads: channel.messagePaginator.aggregateState (latestMessage), and channel.state (membership, muteStatus and the current user's entry in read). While it's mounted, it also starts the draft event handling of the channel's composer, so a draft saved on another device shows in the row.
  • Re-renders: when the latest message, the membership, the mute status or the unread count changes.
  • Needs: nothing. Pass the client, for example from useChatContext().

ChannelPreview calls it for each row and passes the results to the ChannelPreview override as props, with lastMessage translated into the user's language when it has a translation. A row of your own given to WithComponents as ChannelPreview receives them already.

useChannelPreviewDisplayName

Returns the name to show for a channel.

useChannelPreviewDisplayName(channel?: Channel): string
  • Returns, the first that applies:
    1. the channel's name, from channel.data.custom.name;
    2. with one other member, that member's name, or their ID when they have no name;
    3. with more, the first names of the other members, sorted, two of them, followed by the translated "and N others" when more remain.
  • Reads: channel.state, fields data and members, with a pick of each member's ID and name, and the current user's ID.
  • Re-renders: when the name changes, when a member joins or leaves, and when a member's name changes.
  • Needs: Chat. Throws outside it.

The names are joined with ", " and always followed by a space, which separates them from "and N others". Without a channel, or in a channel whose only member is the current user, the result is a single space. The default row, the thread list row and the channel details profile show it.

useChannelPreviewDisplayPresence

Says whether a row shows the online dot on its avatar.

useChannelPreviewDisplayPresence(channel: Channel): boolean
  • Returns: true when the channel has exactly two loaded members and the other one is online.
  • Reads: channel.state.members when the hook renders and when a user.presence.changed or user.updated event arrives. It doesn't subscribe to channel.state.
  • Re-renders: when one of those events changes the answer.
  • Needs: Chat. Throws outside it.

useChannelPreviewDraftMessage

Returns what the user left in a channel's composer.

useChannelPreviewDraftMessage(params: { channel: Channel }): DraftMessage | undefined
  • Returns: { id, text, attachments, custom } with the text and attachments in channel.messageComposer. undefined when the composer is empty, or holds neither text nor attachments, such as only a quoted message.
  • Reads: textComposer.state (text) and attachmentManager.state (attachments) of channel.messageComposer.
  • Re-renders: when the text or the attachments change.
  • Needs: nothing.

It reads the channel's own composer, not a thread's. The default row shows "Draft:" and the text with it. Drafts explains when drafts are saved.

useChannelPreviewPollLabel

Returns the label for the latest vote on a poll.

useChannelPreviewPollLabel(params: { pollId: string }): string | undefined
  • Returns: "You voted: Lunch" when the latest vote is the current user's, "Bob voted: Lunch" otherwise, from the channelPreview.pollVote.byYou.label and channelPreview.pollVote.withUser.label keys. undefined while the poll has no votes, or isn't in client.polls.
  • Reads: poll.state, fields latest_votes_by_option and options, with the poll from client.polls.fromState(pollId).
  • Re-renders: when the votes or the options change.
  • Needs: Chat. Throws outside it.

The default row shows it when the latest message is a poll.

useChannelTypingState

Returns the users typing in a channel.

useChannelTypingState(params: { channel: Channel }): { usersTyping: UserResponse[] }
  • Returns: { usersTyping }, the users typing in the channel's main list. Typing in a thread and the current user's own typing are left out.
  • Reads: channel.state, field typing.
  • Re-renders: when typing changes.
  • Needs: Chat. Throws outside it.

ChannelPreviewTypingIndicator builds its text from it. Inside a Channel or Thread, useTypingString() on Channel and thread hooks returns a ready-made text instead.

useIsChannelMuted

Returns a channel's mute status.

useIsChannelMuted(channel: Channel): ChannelMuteStatus
  • Returns: { muted, createdAt, expiresAt } for the current user. muted is false until the channel's state says otherwise.
  • Reads: channel.state, field muteStatus.
  • Re-renders: when muteStatus changes.
  • Needs: nothing.

For a direct chat, useChannelMuteActive(channel) on Channel and thread hooks also counts a muted member.

useIsChannelPinned

Says whether the current user pinned a channel.

useIsChannelPinned(channel: Channel): boolean
  • Returns: true when the user's membership has a pinned_at.
  • Reads: channel.state, field membership.
  • Re-renders: when membership changes.
  • Needs: nothing.

useMessagePreviewText

Returns the one-line text for a message in a preview.

useMessagePreviewText(params: {
  message?: LocalMessage | MessageResponse | DraftMessage | null;
}): string | undefined
  • Returns, the first that applies:

    1. "Message deleted" for a deleted message;
    2. the poll's name, for a poll;
    3. "Live Location" or "Location", for a shared location;
    4. "Giphy", for a Giphy;
    5. the message's text;
    6. '' without text or attachments;
    7. for attachments of one kind, "Photo", "3 Photos", "Video", "Audio", "Voice message (0:12)", "2 Voice messages", or a single file's title;
    8. otherwise "N Files".

    Every label comes from a message.* or messagePreview.* translation key.

  • Reads: poll.state, field name, for a poll message. The rest comes from the message.

  • Re-renders: when the poll's name changes. A new message gives a new text on the next render.

  • Needs: Chat. Throws outside it.

The channel list row, the thread list row and quoted messages use it.

useMessagePreviewIcon

Returns the icon for a message in a preview.

useMessagePreviewIcon(params: {
  message?: LocalMessage | MessageResponse | DraftMessage | null;
}): React.ComponentType<IconProps> | null
  • Returns: an icon from the icons map of ComponentsContext, the first that applies: CircleBan for a deleted message, PollIcon for a poll, MapPin for a location, Link for a link preview, Mic for audio or voice recordings only, VideoIcon for videos only, PhotoIcon for images only, and File for any other attachment. null without a message, for text only, or for attachments of no known kind, such as a link preview without an image.
  • Reads: ComponentsContext, so an icon you replace through WithComponents is the one returned.
  • Re-renders: never by itself.
  • Needs: nothing.

useMessageDeliveryStatus

Returns whether the current user's latest message was sent, delivered or read.

useMessageDeliveryStatus(params: {
  channel: Channel;
  lastMessage: LocalMessage | MessageResponse;
  isReadEventsEnabled: boolean;
}): { status: MessageDeliveryStatus | undefined }
  • Returns: { status }:
    • MessageDeliveryStatus.READ when another user's read position is on the message or after it;
    • MessageDeliveryStatus.DELIVERED when another user's delivery position is on it or after it;
    • MessageDeliveryStatus.SENT otherwise;
    • undefined when the message isn't the current user's;
    • MessageDeliveryStatus.NOT_SENT_BY_CURRENT_USER whenever isReadEventsEnabled is false, whoever sent the message.
  • Reads: channel.messageReceiptsTracker.snapshotStore, its lastReadRefByOthers and lastDeliveredRefByOthers, compared with the message's created_at.
  • Re-renders: when the message becomes delivered or read.
  • Needs: Chat. Throws outside it.

It makes the same comparison as useIsMessageReadByOthers on Message hooks, so a message stays read once another user reads a later one. The default row passes whether read events are on for the channel, from channel.configState.

useGroupedAttachments

Sorts a message's attachments by kind.

useGroupedAttachments(attachments?: Attachment[]): {
  audios: Attachment[];
  files: Attachment[];
  giphys: Attachment[];
  images: Attachment[];
  videos: Attachment[];
  voiceRecordings: VoiceRecordingAttachment[];
}
  • Returns: the attachments by kind. One that fits no kind, such as a link preview without an image, is left out.
  • Reads: its argument only.
  • Re-renders: never by itself.
  • Needs: nothing.

Channel actions

The swipe actions of a row and the actions of ChannelDetailsBottomSheet come from three hooks:

  • useChannelActions(channel) returns the handlers: pin, unpin, archive, unarchive, muteChannel, unmuteChannel, muteUser, unmuteUser, blockUser, unblockUser, leave, deleteChannel, addMembers, removeMembers, updateName and updateImage. Each publishes a success or failure notification, and calls the onSuccess or onFailure you pass in its options.
  • useChannelActionItems({ channel, surface, getChannelActionItems }) returns the items a list or details screen shows, built from those handlers.
  • useChannelActionItemsById(params) returns the same items keyed by id.

They need Chat. Channel actions explains the items and how to change them, and Running channel and user actions the handlers.

Example

A compact row, given to WithComponents as ChannelPreview. It receives the values of useChannelPreviewData as props, and reads the rest with hooks:

import { Pressable, Text, View } from "react-native";
import {
  useChannelPreviewDisplayName,
  useChannelPreviewDraftMessage,
  useChannelsContext,
  useMessagePreviewText,
} from "stream-chat-react-native";
import type { ChannelPreviewViewProps } from "stream-chat-react-native";

export const CompactChannelPreview = ({
  channel,
  lastMessage,
  muted,
  unread,
}: ChannelPreviewViewProps) => {
  const { onSelect } = useChannelsContext();
  const displayName = useChannelPreviewDisplayName(channel);
  const draft = useChannelPreviewDraftMessage({ channel });
  const previewText = useMessagePreviewText({ message: draft ?? lastMessage });

  return (
    <Pressable accessibilityRole="button" onPress={() => onSelect?.(channel)}>
      <View style={{ flexDirection: "row", padding: 12 }}>
        <View style={{ flex: 1 }}>
          <Text numberOfLines={1}>{displayName}</Text>
          <Text numberOfLines={1}>
            {draft ? `Draft: ${previewText}` : previewText}
          </Text>
        </View>
        {!muted && unread ? <Text>{unread}</Text> : null}
      </View>
    </Pressable>
  );
};

Declare the override object at module scope, const overrides = { ChannelPreview: CompactChannelPreview }, as Overriding components explains.