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, frommembership.pinned_at.unread: the current user's unread count.0while the channel is muted.
- Reads:
channel.messagePaginator.aggregateState(latestMessage), andchannel.state(membership,muteStatusand the current user's entry inread). 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:
- the channel's name, from
channel.data.custom.name; - with one other member, that member's name, or their ID when they have no name;
- with more, the first names of the other members, sorted, two of them, followed by the translated "and N others" when more remain.
- the channel's name, from
- Reads:
channel.state, fieldsdataandmembers, with apickof 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:
truewhen the channel has exactly two loaded members and the other one is online. - Reads:
channel.state.memberswhen the hook renders and when auser.presence.changedoruser.updatedevent arrives. It doesn't subscribe tochannel.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 inchannel.messageComposer.undefinedwhen the composer is empty, or holds neither text nor attachments, such as only a quoted message. - Reads:
textComposer.state(text) andattachmentManager.state(attachments) ofchannel.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.labelandchannelPreview.pollVote.withUser.labelkeys.undefinedwhile the poll has no votes, or isn't inclient.polls. - Reads:
poll.state, fieldslatest_votes_by_optionandoptions, with the poll fromclient.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, fieldtyping. - Re-renders: when
typingchanges. - 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.mutedisfalseuntil the channel's state says otherwise. - Reads:
channel.state, fieldmuteStatus. - Re-renders: when
muteStatuschanges. - 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:
truewhen the user's membership has apinned_at. - Reads:
channel.state, fieldmembership. - Re-renders: when
membershipchanges. - 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:
- "Message deleted" for a deleted message;
- the poll's name, for a poll;
- "Live Location" or "Location", for a shared location;
- "Giphy", for a Giphy;
- the message's text;
''without text or attachments;- for attachments of one kind, "Photo", "3 Photos", "Video", "Audio", "Voice message (0:12)", "2 Voice messages", or a single file's title;
- otherwise "N Files".
Every label comes from a
message.*ormessagePreview.*translation key. -
Reads:
poll.state, fieldname, for a poll message. The rest comes from the message. -
Re-renders: when the poll's name changes. A new
messagegives 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
iconsmap ofComponentsContext, the first that applies:CircleBanfor a deleted message,PollIconfor a poll,MapPinfor a location,Linkfor a link preview,Micfor audio or voice recordings only,VideoIconfor videos only,PhotoIconfor images only, andFilefor any other attachment.nullwithout 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 throughWithComponentsis 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.READwhen another user's read position is on the message or after it;MessageDeliveryStatus.DELIVEREDwhen another user's delivery position is on it or after it;MessageDeliveryStatus.SENTotherwise;undefinedwhen the message isn't the current user's;MessageDeliveryStatus.NOT_SENT_BY_CURRENT_USERwheneverisReadEventsEnabledisfalse, whoever sent the message.
- Reads:
channel.messageReceiptsTracker.snapshotStore, itslastReadRefByOthersandlastDeliveredRefByOthers, compared with the message'screated_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,updateNameandupdateImage. Each publishes a success or failure notification, and calls theonSuccessoronFailureyou 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 byid.
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.