# Channel lists

> **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/).

A channel list is the screen of conversations a user picks from. This page covers what users can do in it, which state backs it, and what each row action calls. How the list renders and which parts you can replace is on [Channel list UI](https://getstream.io/chat/docs/sdk/react-native/v10/ui/components/channel-list-ui/). How lists load and stay current is on [Channel list paginators](https://getstream.io/chat/docs/sdk/react-native/v10/state/loading-data/channel-list-paginators/).

## What users get

The user opens the app and sees their channels, the latest activity first. Each row shows the channel's avatar and name, the time and a preview of the latest message, and a badge with the number of unread messages. A pin icon marks a channel they pinned, and a mute icon a channel they muted. Scrolling down loads more channels, pulling the list down refreshes it, and tapping a row opens the channel.

When the user swipes a row toward the start, two buttons appear. One mutes the channel, and "more" opens a bottom sheet with the channel's avatar and name, and the actions Pin and Leave. In a direct message channel the sheet also has Mute User and Block User, and when the user created the channel it has Delete. Leaving, deleting and blocking ask them to confirm first. Each action confirms its result with a notification, such as "Channel pinned".

The list keeps up as things change. When a message arrives, its channel moves to the top of a list sorted by the latest message. When the user pins a channel, a list sorted on `pinned_at` moves it to the top. When they archive one, a list that leaves archived channels out drops it, and a list of archived channels picks it up.

<table>
  <tr>
  <td align='center' width='50%'>

![A channel list row swiped open with a mute button and a more button on iOS](https://getstream.io/docs-assets/images/b0bf2a67d4bc.png)

  </td>
  <td align='center' width='50%'>

![A channel list row swiped open with a mute button and a more button on Android](https://getstream.io/docs-assets/images/0e330cfabe66.png)

  </td>
  </tr>
</table>

## How it works across the layers

### State: the lists and their channels

A list is a `ChannelPaginator`: a query with filters and a sort order, and the channels it has loaded. The app creates it and registers it on `client.channelManager`. The manager keeps every registered list current as events arrive, and decides which list holds a channel that matches several. [Channel list paginators](https://getstream.io/chat/docs/sdk/react-native/v10/state/loading-data/channel-list-paginators/) explains both.

Each row is a `Channel` object, the same one the open channel and the rest of the app use. The row shows what the channel's stores hold, so a change made anywhere shows in the row too.

Three things about a channel belong to the current user alone: whether they archived it, pinned it or muted it.

- **Archived** and **pinned** are dates on the user's membership, `archived_at` and `pinned_at`. A list can filter on `archived` and `pinned` and sort on `pinned_at`, and the client applies the same filters and sort when they change.
- **Muted** is the channel's `muteStatus`. A muted channel's new messages don't count as unread.

[What channels and threads hold](https://getstream.io/chat/docs/sdk/react-native/v10/state/channels-and-threads/what-channels-and-threads-hold/#membership) has the fields and the events that change them.

### UI: the list and its rows

`<ChannelList paginator={paginator} onSelect={onSelect} />` renders one paginator. It loads the first page when the paginator has none, and the next page when the user scrolls near the end. It also starts the channel manager's event subscriptions, which every mounted list shares. The SDK has no component that renders every registered list, so render one `ChannelList` per paginator, as [Several lists](https://getstream.io/chat/docs/sdk/react-native/v10/ui/components/channel-list-ui/#several-lists) shows.

Each row reads its own channel's stores, so a row re-renders only when its channel changes. The unread badge is hidden while the channel is muted. [Channel list UI](https://getstream.io/chat/docs/sdk/react-native/v10/ui/components/channel-list-ui/#rows) lists what each part of a row reads.

The list holds no selection and doesn't navigate. Tapping a row calls `onSelect(channel)`, and your navigator shows the channel. Without `onSelect`, tapping a row does nothing. See [Navigation](https://getstream.io/chat/docs/sdk/react-native/v10/ui/layout/navigation/).

### The row actions

The row actions are channel action items. The SDK builds them with `buildDefaultChannelActionItems`, and each item has a `placement` that decides where it shows: in the swipe row, in the bottom sheet that "more" opens, or in both. Each one calls a method of the row's channel or of the client:

| Action                   | Where     | What it calls                                                                                                 | Shown when                   |
| ------------------------ | --------- | ------------------------------------------------------------------------------------------------------------- | ---------------------------- |
| Mute, Unmute             | Swipe row | `Channel.mute()`, `Channel.unmute()`                                                                          | Always                       |
| Pin, Unpin               | Sheet     | `Channel.pin()`, `Channel.unpin()`                                                                            | Always                       |
| Mute User, Unmute User   | Sheet     | `client.moderation.mute({ target_ids })`, `client.moderation.unmute({ target_ids })` for the other member     | A direct message channel     |
| Block User, Unblock User | Sheet     | Asks to confirm a block, then `client.blockUser(userId)` or `client.unblockUser(userId)` for the other member | A direct message channel     |
| Leave                    | Sheet     | Asks to confirm, then `Channel.removeMembers({ members: [client.userId] })`                                   | Always                       |
| Delete                   | Sheet     | Asks to confirm, then `Channel.delete()`                                                                      | The user created the channel |

A direct message channel is one with exactly one member besides the current user. The labels read "Chat" in a direct message channel and "Group" in other channels, such as "Mute Chat" and "Leave Group". The items don't check the user's capabilities: a user without `leave-channel` sees Leave, and the request fails with an error notification.

None of these actions changes the row before the server answers. The row and the lists change when the server's response or event arrives:

- **Mute.** `notification.channel_mutes_updated` updates the channel's `muteStatus`. The row shows the mute icon and hides the unread badge, and lists filtered on `muted` move the channel.
- **Pin.** `member.updated` updates the user's membership. Lists filtered on `pinned`, or sorted on `pinned_at`, move the channel.
- **Block.** `client.blockedUsers` adds the user when the response arrives, and the item reads Unblock User.
- **Leave and delete.** `notification.removed_from_channel` and `channel.deleted` remove the channel from every list.

Each action reports its result through the notification list of the channel list: "Channel muted", "Channel pinned", "Left channel", "User blocked" or "Channel deleted", or an error such as "Failed to leave channel". [What's optimistic and what isn't](https://getstream.io/chat/docs/sdk/react-native/v10/state/writing-messages/whats-optimistic-and-what-isnt/) lists which state changes wait for the server.

Block User here blocks the other member with `client.blockUser()`, as the message menu and channel details do. It doesn't ban them from the channel. [Flagging, muting, blocking and bans](https://getstream.io/chat/docs/sdk/react-native/v10/features/moderation-and-safety/flagging-muting-blocking-and-bans/) explains the difference.

### Archiving

The rows have no Archive action, but the state layer has one. `Channel.archive()` and `Channel.unarchive()` set and clear the user's `archived_at`, and `useChannelActions(channel)` returns `archive` and `unarchive` handlers that also publish a notification. Like pinning, the lists move the channel when `member.updated` arrives. [Which channels a list shows](#which-channels-a-list-shows-and-in-what-order) adds an Archive action to the sheet.

### Hiding a channel

The rows have no Hide action either. `Channel.hide()` hides the channel for the current user until a new message arrives in it, and `Channel.show()` shows it again. `Channel.hide({ clear_history: true })` also clears the channel's history for the user.

A hidden channel leaves every list whose filters don't mention `hidden`, when the server's `channel.hidden` event arrives. It comes back on `channel.visible`.

## Turning it on or off

A channel list shows wherever you render `ChannelList`. It shows the channels that match its paginator's filters, so scope them to the user, for example with `members: { $in: [userId] }`. Without such a filter, the list can show channels the user isn't a member of.

`swipeActionsEnabled={false}` turns swiping off, and the rows then have no actions:

```tsx
<ChannelList
  onSelect={onSelect}
  paginator={paginator}
  swipeActionsEnabled={false}
/>
```

The server decides what each action is allowed to do, from the user's roles and the channel type's permissions. Pin, archive and mute change the user's own settings, so membership is enough. Leaving needs `leave-channel` and deleting needs `delete-channel`. Change permissions on the server; see [Permissions reference](https://getstream.io/chat/docs/javascript/permissions-reference/).

## Customizing

### Which channels a list shows, and in what order

The paginator's filters and sort decide this. This screen shows an inbox with pinned channels on top, and switches to a second list with the archived channels. It also adds Archive and Unarchive to the bottom sheet:

```tsx title="src/ChannelsScreen.tsx"
import { useEffect, useMemo, useState } from "react";
import { Button, StyleSheet, View } from "react-native";
import { ChannelPaginator } from "stream-chat";
import type { Channel, ChannelFilters, SortParamRequest } from "stream-chat";
import {
  asDynamicKey,
  ChannelList,
  useChatContext,
} from "stream-chat-react-native";
import type {
  ChannelActionItem,
  GetChannelActionItems,
} from "stream-chat-react-native";

const userId = "YOUR_USER_ID";

// Pinned channels first, the most recently pinned on top, then the latest message first.
const sort: SortParamRequest[] = [
  { direction: -1, field: "pinned_at" },
  { direction: -1, field: "last_message_at" },
];

const inboxFilters: ChannelFilters = {
  archived: false,
  members: { $in: [userId] },
};
const archiveFilters: ChannelFilters = {
  archived: true,
  members: { $in: [userId] },
};

// Declared at module scope, so the list doesn't rebuild its items on every render.
const getChannelActionItems: GetChannelActionItems = ({
  context,
  defaultItems,
}) => {
  const { actions, icons, isArchived, t } = context;

  const archive: ChannelActionItem = {
    action: isArchived ? actions.unarchive : actions.archive,
    Icon: icons.Folder,
    id: "archive",
    label: isArchived
      ? t(asDynamicKey("myApp.channel.unarchive.label"), "Unarchive")
      : t(asDynamicKey("myApp.channel.archive.label"), "Archive"),
    placement: "sheet",
    type: "standard",
  };

  return [...defaultItems, archive];
};

export const ChannelsScreen = ({
  onSelect,
}: {
  onSelect: (channel: Channel) => void;
}) => {
  const { client } = useChatContext();
  const [showArchive, setShowArchive] = useState(false);

  const [inbox, archive] = useMemo(
    () => [
      new ChannelPaginator({
        client,
        filters: inboxFilters,
        id: "inbox",
        sort,
      }),
      new ChannelPaginator({
        client,
        filters: archiveFilters,
        id: "archive",
        sort,
      }),
    ],
    [client],
  );

  // Both lists are registered, so both stay current while the other one is on screen.
  useEffect(() => {
    client.channelManager.insertPaginator({ paginator: inbox });
    client.channelManager.insertPaginator({ paginator: archive });
    return () => {
      client.channelManager.removePaginator(inbox);
      client.channelManager.removePaginator(archive);
      inbox.dispose();
      archive.dispose();
    };
  }, [archive, client, inbox]);

  return (
    <View style={styles.container}>
      <Button
        onPress={() => setShowArchive((value) => !value)}
        title={showArchive ? "Show inbox" : "Show archived"}
      />
      <ChannelList
        getChannelActionItems={getChannelActionItems}
        onSelect={onSelect}
        paginator={showArchive ? archive : inbox}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1 },
});
```

When the user archives a channel from the inbox, `member.updated` arrives, and the channel moves to the archive list. When they pin one, it moves to the top of its list.

A list whose filters or sort change has to load again from the first page; see [Changing filters or sort](https://getstream.io/chat/docs/sdk/react-native/v10/state/loading-data/channel-list-paginators/#changing-filters-or-sort). The [Pinning and archiving](https://getstream.io/chat/docs/sdk/react-native/v10/ui-cookbook/channel-list/pinning-and-archiving/) UI cookbook builds pin and archive controls into a custom row.

### What a row looks like

Replace the row, its parts or its message line through `WithComponents`. [Channel list UI](https://getstream.io/chat/docs/sdk/react-native/v10/ui/components/channel-list-ui/#replacing-parts) lists the override keys and has an example, and the [Preview UI](https://getstream.io/chat/docs/sdk/react-native/v10/ui-cookbook/channel-list/preview-ui/) UI cookbook builds a custom row.

### The row actions

`getChannelActionItems` on `ChannelList` receives the default items and returns the list to show. Filter an item out by `id`, change its `placement` to move it between the swipe row and the sheet, or add your own, as the sample above does. [Channel actions](https://getstream.io/chat/docs/sdk/react-native/v10/ui/customizing/actions/#channel-actions) explains the items and the hooks behind them, and the [Custom channel actions](https://getstream.io/chat/docs/sdk/react-native/v10/ui-cookbook/channel-details/custom-channel-actions/) UI cookbook builds more.

### Where a channel opens

Your navigator decides, in `onSelect`. [Moving between channels, threads and messages](https://getstream.io/chat/docs/sdk/react-native/v10/ui/layout/moving-between-channels-threads-and-messages/#opening-a-channel) covers every way a channel opens.

## Reference

- [ChannelList](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/channel-list/channellist/): the `ChannelList` props.
- [Channel preview](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/channel-list/channel-preview/): the row components and their props.
- [ChannelsContext](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/channel-list/channelscontext/): the context below `ChannelList`.
- [ComponentsContext](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/channel/componentscontext/): the override keys, including `ChannelPreview` and `ChannelDetailsBottomSheet`.
- [Channel manager and ChannelPaginator](https://getstream.io/chat/docs/sdk/react-native/v10/reference/state/channel-manager-and-channelpaginator/): the state APIs.
- [Channel preview hooks](https://getstream.io/chat/docs/sdk/react-native/v10/reference/hooks/channel-preview-hooks/): hooks such as `useChannelPreviewDisplayName`.
- [Channel and thread hooks](https://getstream.io/chat/docs/sdk/react-native/v10/reference/hooks/channel-and-thread-hooks/): hooks such as `useChannelMembershipState`.
- [Loading data hooks](https://getstream.io/chat/docs/sdk/react-native/v10/reference/hooks/loading-data-hooks/): hooks for reading a paginator's state.

---

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