# Custom ChannelList

This guide shows common customizations for [`ChannelList`](https://getstream.io/chat/docs/sdk/react-native/v8/core-components/channel-list/).

## Best Practices

- Keep `filters` aligned with any event-handler overrides to avoid inconsistent list state.
- Scope custom event handlers to the specific behavior you want; fall back to defaults otherwise.
- Use `channelRenderFilterFn` when multiple lists are mounted to prevent cross-list reordering.
- Prefer `additionalFlatListProps` for pagination tweaks instead of re-implementing list logic.
- Guard `loadNextPage` calls with `hasNextPage` and `loadingChannels` to avoid duplicate fetches.

### Customizing Event Handlers

`ChannelList` uses [event listeners](https://getstream.io/chat/docs/javascript/event-object/) to update when changes occur.
If a new message is received, a user is added to a channel, or other events take place, the `ChannelList` will update its UI accordingly.
Note: [`filters`](https://getstream.io/chat/docs/sdk/react-native/v8/core-components/channel-list#filters) do not apply to list updates triggered by events.

You can override each event handler via props:

| [Event Type](https://getstream.io/chat/docs/javascript/event-object/) | Default Behavior                                                   | Prop to override                                                                                                                                                                           |
| ------------------------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `channel.deleted`                                 | Remove channel from the list                                       | [onChannelDeleted](https://getstream.io/chat/docs/sdk/react-native/v8/core-components/channel-list#onchanneldeleted)                                                                                           |
| `channel.hidden`                                  | Remove channel from the list                                       | [onChannelHidden](https://getstream.io/chat/docs/sdk/react-native/v8/core-components/channel-list#onchannelhidden)                                                                                             |
| `channel.truncated`                               | Updates the channel                                                | [onChannelTruncated](https://getstream.io/chat/docs/sdk/react-native/v8/core-components/channel-list#onchanneltruncated)                                                                                       |
| `channel.updated`                                 | Updates the channel                                                | [onChannelUpdated](https://getstream.io/chat/docs/sdk/react-native/v8/core-components/channel-list#onchannelupdated)                                                                                           |
| `channel.visible`                                 | Adds the channel to the list                                       | [onChannelVisible](https://getstream.io/chat/docs/sdk/react-native/v8/core-components/channel-list#onchannelvisible)                                                                                           |
| `message.new`                                     | Moves the channel to top of the list                               | [lockChannelOrder](https://getstream.io/chat/docs/sdk/react-native/v8/core-components/channel-list#lockchannelorder), [onNewMessage](https://getstream.io/chat/docs/sdk/react-native/v8/core-components/channel-list#onnewmessage) |
| `notification.added_to_channel`                   | Adds the new channel to the top of the list and starts watching it | [onAddedToChannel](https://getstream.io/chat/docs/sdk/react-native/v8/core-components/channel-list#onaddedtochannel)                                                                                           |
| `notification.message_new`                        | Adds the new channel to the top of the list and starts watching it | [onNewMessageNotification](https://getstream.io/chat/docs/sdk/react-native/v8/core-components/channel-list#onnewmessagenotification)                                                                           |
| `notification.removed_from_channel`               | Removes the channel from the list                                  | [onRemovedFromChannel](https://getstream.io/chat/docs/sdk/react-native/v8/core-components/channel-list#onremovedfromchannel)                                                                                   |

Example: a `ChannelList` of [frozen channels](https://getstream.io/chat/docs/javascript/disabling-channels/).

```tsx
const filters = {
  members: { $in: ['vishal'] },
  frozen: true
}

<ChannelList filters={filters} />
```

`notification.message_new` fires when a message is received on a channel that isn't loaded but includes the current user.
By default, the SDK queries that channel and adds it to the top of the list, regardless of [`filters`](https://getstream.io/chat/docs/sdk/react-native/v8/core-components/channel-list#filters). That means unfrozen channels may appear in a frozen-only list.

Override this behavior with a custom [`onNewMessageNotification`](https://getstream.io/chat/docs/sdk/react-native/v8/core-components/channel-list#onnewmessagenotification) on `ChannelList`.
It receives `setChannels` (state setter) and the `event` for `notification.message_new`.

```tsx {6-19,23}
const filters = {
  members: { $in: ["vishal"] },
  frozen: true,
};

const customOnNewMessageNotification = async (setChannels, event) => {
  const eventChannel = event.channel;

  // If the channel is frozen, then don't add it to the list.
  if (!eventChannel?.id || !eventChannel.frozen) return;

  try {
    const newChannel = client.channel(eventChannel.type, eventChannel.id);
    await newChannel.watch();
    setChannels((channels) => [newChannel, ...channels]);
  } catch (error) {
    console.log(error);
  }
};

<ChannelList
  filters={filters}
  onNewMessageNotification={customOnNewMessageNotification}
/>;
```

Handle other events similarly as needed.

### Replacing infinite scroll with a "Load More" button

`ChannelList` component accepts the [`List`](https://getstream.io/chat/docs/sdk/react-native/v8/core-components/channel-list#list) prop, which is a component to render the list of channels.
Default is [`ChannelListMessenger`](https://getstream.io/chat/docs/sdk/react-native/v8/ui-components/channel-preview-messenger/), which consumes [`ChannelsContext`](https://getstream.io/chat/docs/sdk/react-native/v8/contexts/channels-context/) and uses `FlatList` with [`loadNextPage`](https://getstream.io/chat/docs/sdk/react-native/v8/contexts/channels-context#loadnextpage)
from `ChannelsContext` is attached to [onEndReached](https://reactnative.dev/docs/flatlist#onendreached) prop of underlying FlatList to allow infinite scroll pagination.

Override FlatList props via [`additionalFlatListProps`](https://getstream.io/chat/docs/sdk/react-native/v8/core-components/channel-list#additionalflatlistprops) on `ChannelList` or `ChannelListMessenger`. Add a footer button and disable infinite scroll by overriding `onEndReached`.

The "Load More" button should call [`loadNextPage`](https://getstream.io/chat/docs/sdk/react-native/v8/contexts/channels-context#loadnextpage). Use `hasNextPage` and `loadingChannels` to control visibility.

![ChannelList](https://getstream.io/docs-assets/images/10fcb2b48c80.png)

```tsx
import { Button } from "react-native";
import { useChannelsContext } from "stream-chat-react-native";

const FooterLoadMoreButton = () => {
  const { loadingChannels, loadNextPage, hasNextPage } = useChannelsContext();

  if (loadingChannels || !hasNextPage) return null;

  return <Button title={"Load More"} onPress={loadNextPage} />;
};

<ChannelList
  additionalFlatListProps={{
    ListFooterComponent: FooterLoadMoreButton,
    onEndReached: () => null,
  }}
/>;
```

## Multiple Channel Lists

This example will focus on the specific use case where there are two `ChannelList` components in the same application.

With two lists (A and B), both will process all `message.new` events. A message in list B can reorder list A. Use custom rendering filters to avoid this.

### Using `channelRenderFilterFn` prop

By default, `ChannelList` pulls missing channels from `client.activeChannels`, which can reorder lists unexpectedly.

Use `channelRenderFilterFn` to apply custom filtering logic (type, custom fields, etc.) to rendered channels.

```tsx
const customChannelFilterFunction = (channels: Channel[]) => {
  return channels.filter(/** your custom filter logic */);
};

<ChannelList
  channelRenderFilterFn={customChannelFilterFunction}
  filters={filters}
/>;
```

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/v8/guides/customize-channel-list/](https://getstream.io/chat/docs/sdk/react-native/v8/guides/customize-channel-list/).