# Channel preview hooks

> **Info:** This is release candidate documentation for **Stream Chat React Native SDK v10**. For the latest stable version, see the [latest version (v9)](https://getstream.io/chat/docs/sdk/react-native/).

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](https://getstream.io/chat/docs/sdk/react-native/v10/ui/components/channel-list-ui/#rows), and the row components on [Channel preview](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/channel-list/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.

```ts
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.

```ts
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.

```ts
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.

```ts
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](https://getstream.io/chat/docs/sdk/react-native/v10/features/messaging/drafts/) explains when drafts are saved.

## useChannelPreviewPollLabel

Returns the label for the latest vote on a poll.

```ts
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.

```ts
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](https://getstream.io/chat/docs/sdk/react-native/v10/reference/hooks/channel-and-thread-hooks/#usetypingstring) returns a ready-made text instead.

## useIsChannelMuted

Returns a channel's mute status.

```ts
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](https://getstream.io/chat/docs/sdk/react-native/v10/reference/hooks/channel-and-thread-hooks/#usechannelmuteactive) also counts a muted member.

## useIsChannelPinned

Says whether the current user pinned a channel.

```ts
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.

```ts
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.

```ts
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.

```ts
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](https://getstream.io/chat/docs/sdk/react-native/v10/reference/hooks/message-hooks/#useismessagereadbyothers), 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.

```ts
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](https://getstream.io/chat/docs/sdk/react-native/v10/ui/customizing/actions/#channel-actions) explains the items and how to change them, and [Running channel and user actions](https://getstream.io/chat/docs/sdk/react-native/v10/ui/customizing/actions/#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:

```tsx
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](https://getstream.io/chat/docs/sdk/react-native/v10/ui/customizing/overriding-components/) explains.

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/v10/reference/hooks/channel-preview-hooks/](https://getstream.io/chat/docs/sdk/react-native/v10/reference/hooks/channel-preview-hooks/).