Info:
This is release candidate documentation for Stream Chat React Native SDK v10. For the latest stable version, see the latest version (v9).

ChannelList

ChannelList renders the channels of one ChannelPaginator, a row per channel, and loads the next page as the user scrolls. The paginator holds the query and the loaded channels; the list only renders them. Channel list UI explains what the list renders and reads, and Channel lists covers the feature end to end.

Best practices

  • Create the paginator once for the user, not on every render, and register it on client.channelManager. An unregistered paginator shows its first page and then never changes.
  • Scope the paginator's filters to the user, for example with members: { $in: [userId] }. Without that, a list can show channels the user isn't a member of.
  • Give each list its own paginator with its own id.
  • Remove and dispose of the paginator when the user signs out. ChannelList never disposes of it.
  • Pass onSelect, and open the channel from it. The list holds no selection and doesn't navigate.

Rendering a list

Create a paginator, register it on client.channelManager, and pass it to ChannelList:

src/ChannelListScreen.tsx
import { useEffect, useMemo } from "react";
import { ChannelPaginator } from "stream-chat";
import type { Channel, ChannelFilters, SortParamRequest } from "stream-chat";
import { ChannelList, useChatContext } from "stream-chat-react-native";

const sort: SortParamRequest[] = [{ direction: -1, field: "last_message_at" }];

export const ChannelListScreen = ({
  onSelect,
  userId,
}: {
  onSelect: (channel: Channel) => void;
  userId: string;
}) => {
  const { client } = useChatContext();

  const paginator = useMemo(() => {
    const filters: ChannelFilters = {
      members: { $in: [userId] },
      type: "messaging",
    };
    return new ChannelPaginator({ client, filters, id: "inbox", sort });
  }, [client, userId]);

  // A registered list is kept up to date as events arrive.
  useEffect(() => {
    client.channelManager.insertPaginator({ paginator });
    return () => {
      client.channelManager.removePaginator(paginator);
      paginator.dispose();
    };
  }, [client, paginator]);

  return <ChannelList onSelect={onSelect} paginator={paginator} />;
};

Here the screen owns the paginator, so it's disposed of when the screen unmounts. An app that keeps the list's channels across screens creates the paginator next to the client instead. Filters, sort, registering and disposing are explained on Channel list paginators.

What it does

  • Loads the first page when the paginator has none and a user is connected: on mount, when it gets another paginator, and after client.disconnectUser() resets the paginator. A paginator that already holds channels is shown as it is.
  • Loads the next page when the user scrolls within loadMoreThreshold of the end.
  • Refreshes the first page when the user pulls the list down, keeping the loaded channels on screen. A pull within 5 seconds of the last refresh, or of the list mounting, is ignored unless the last query failed.
  • Starts the event subscriptions of client.channelManager while it's mounted. Every mounted list shares them, and they stop when the last list unmounts.
  • Provides ChannelsContext to its rows, and renders a NotificationList for the notifications aimed at the list.

It renders the rows in ChannelListView, a React Native FlatList.

UI customization

Register replacements with WithComponents. ChannelList and the parts it renders read these keys:

Override key Replaces Gets
ChannelPreview The content of each row. The default is ChannelPreviewView. channel, muted, pinned, unread, lastMessage
ChannelListLoadingIndicator The placeholder rows while the first page loads nothing
Skeleton Each placeholder row nothing
ChannelListFooterLoadingIndicator The spinner after the last row while the next page loads nothing
EmptyStateIndicator "No conversations yet", when the list has no channels listType: 'channel'
LoadingErrorIndicator The error state when the first page fails. Tapping it loads the first page again. error, listType: 'channel', loadNextPage, retry
ChannelListHeaderErrorIndicator The banner over the list when a query fails while channels are on screen onPress, which refreshes the list
ChannelListHeaderNetworkDownIndicator The "Reconnecting..." banner while the network or the WebSocket is down nothing
ListHeaderComponent Nothing by default. Rendered above the first row, and scrolls with the list. nothing
NotificationList The list's notifications nothing

The row's parts, such as the title and the unread badge, have keys of their own; see Channel preview. ChannelList itself has no override key.

EmptyStateIndicator and LoadingErrorIndicator are shared with the message list, which passes listType: 'message', so a replacement checks listType. Loading, empty and error states explains when each one shows.

Props

Prop Type Default Description
paginator ChannelPaginator required The paginator whose channels the list renders. Register it on client.channelManager.
additionalFlatListProps Partial<FlatListProps<Channel>> {} Props spread on the FlatList after the ones the list sets, so they take precedence. Use setFlatListRef for the ref.
channelRenderFilterFn (channels: Array<Channel>) => Array<Channel> Filters the loaded channels before they render. It runs on every render of the list, so keep it a cheap pure function. The paginator still holds every channel.
getChannelActionItems GetChannelActionItems the default items Receives the default channel actions of a row and returns the ones to show. See Changing the items.
loadMoreThreshold number 0.1 How close to the end the user scrolls before the next page loads, in multiples of the list's visible length: the FlatList's onEndReachedThreshold.
maxUnreadCount number 255 The highest count the unread badge shows. A larger count shows as this number.
mutedStatusPosition 'trailingBottom' | 'inlineTitle' 'inlineTitle' Where a muted channel's icon shows: next to the title, or at the end of the message line.
notificationHostId string a random id the list generates once The notification host of the list's NotificationList. See Routing notifications.
numberOfSkeletons number 8 The number of placeholder rows while the first page loads.
onSelect (channel: Channel) => void Runs when the user taps a row, with its channel. While a row's swipe actions are open, a tap closes them instead. Without it, a tap does nothing else.
pinnedStatusPosition 'trailingBottom' | 'inlineTitle' 'inlineTitle' Where a pinned channel's icon shows: next to the title, or at the end of the message line.
setFlatListRef (ref: FlatList<Channel> | null) => void Receives the list's FlatList ref, and null when it unmounts.
swipeActionsEnabled boolean true Lets the user swipe a row to reveal its actions. See Swipe actions.

The FlatList in the type of setFlatListRef is the one from react-native-gesture-handler; the list renders React Native's.

Everything else about a list is set on its paginator and on client.channelManager: filters, sort, page size, and how events move channels between lists. See Channel list paginators and Channel manager and ChannelPaginator.

Examples

Scroll to the top

src/ScrollableChannelList.tsx
import { useRef } from "react";
import { Button, View } from "react-native";
import type { Channel, ChannelPaginator } from "stream-chat";
import { ChannelList } from "stream-chat-react-native";

type ListRef = {
  scrollToOffset: (params: { animated?: boolean; offset: number }) => void;
};

export const ScrollableChannelList = ({
  onSelect,
  paginator,
}: {
  onSelect: (channel: Channel) => void;
  paginator: ChannelPaginator;
}) => {
  const listRef = useRef<ListRef | null>(null);

  return (
    <View style={{ flex: 1 }}>
      <Button
        onPress={() =>
          listRef.current?.scrollToOffset({ animated: true, offset: 0 })
        }
        title="Back to top"
      />
      <ChannelList
        onSelect={onSelect}
        paginator={paginator}
        setFlatListRef={(ref) => {
          listRef.current = ref;
        }}
      />
    </View>
  );
};

Several lists

Register one paginator per list, each with its own id, and render one ChannelList for each. Several lists has an example with tabs.

Change the query

Set the paginator's staticFilters or sort, and reload it, or create a paginator with another id. Changing filters or sort shows both.

  • useChannelsContext() returns ChannelsContext inside a list: the paginator, the list's configuration and its loading functions.
  • useStateStore(paginator.state, selector) reads the loaded channels and whether more are loading. See Loading data hooks.
  • The hooks a row uses, such as useChannelPreviewDisplayName, are on Channel preview hooks.