# Custom ChannelList

This guide shows common customizations for [`ChannelList`](/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](/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`](/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](/chat/docs/javascript/event_object/) | Default Behavior                                                   | Prop to override                                                                                                                                                                             |
| ------------------------------------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `channel.deleted`                                 | Remove channel from the list                                       | [onChannelDeleted](/chat/docs/sdk/react-native/v8/core-components/channel-list#onchanneldeleted/)                                                                                            |
| `channel.hidden`                                  | Remove channel from the list                                       | [onChannelHidden](/chat/docs/sdk/react-native/v8/core-components/channel-list#onchannelhidden/)                                                                                              |
| `channel.truncated`                               | Updates the channel                                                | [onChannelTruncated](/chat/docs/sdk/react-native/v8/core-components/channel-list#onchanneltruncated/)                                                                                        |
| `channel.updated`                                 | Updates the channel                                                | [onChannelUpdated](/chat/docs/sdk/react-native/v8/core-components/channel-list#onchannelupdated/)                                                                                            |
| `channel.visible`                                 | Adds the channel to the list                                       | [onChannelVisible](/chat/docs/sdk/react-native/v8/core-components/channel-list#onchannelvisible/)                                                                                            |
| `message.new`                                     | Moves the channel to top of the list                               | [lockChannelOrder](/chat/docs/sdk/react-native/v8/core-components/channel-list#lockchannelorder/), [onNewMessage](/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](/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](/chat/docs/sdk/react-native/v8/core-components/channel-list#onnewmessagenotification/)                                                                            |
| `notification.removed_from_channel`               | Removes the channel from the list                                  | [onRemovedFromChannel](/chat/docs/sdk/react-native/v8/core-components/channel-list#onremovedfromchannel/)                                                                                    |

Example: a `ChannelList` of [frozen channels](/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`](/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`](/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`](/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`](/chat/docs/sdk/react-native/v8/ui-components/channel-preview-messenger/), which consumes [`ChannelsContext`](/chat/docs/sdk/react-native/v8/contexts/channels-context/) and uses `FlatList` with [`loadNextPage`](/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`](/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`](/chat/docs/sdk/react-native/v8/contexts/channels-context#loadnextpage/). Use `hasNextPage` and `loadingChannels` to control visibility.

![ChannelList](@chat-sdk/react-native/v8/_assets/guides/custom-channel-list/load_more_button.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}
/>;
```


---

This page was last updated at 2026-04-17T17:33:45.079Z.

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