This is beta documentation for Stream Chat React SDK v14. For the latest stable version, see the latest version (v13) .

ChannelList

ChannelList queries channels from the Stream Chat API and renders them as a list. Use filters, sort, and options to customize the underlying Query Channels request.

const channels = await client.queryChannels(filters, sort, options);

Best Practices

  • Always include a members filter to avoid querying the entire app.
  • Keep sort and options consistent with your UX (recency vs. priority lists).
  • Set options.limit explicitly when first-load size and pagination behavior matter.
  • Use ChannelList for navigation state instead of managing active channels manually.
  • Override List/Preview only when you need layout changes beyond styling.
  • Customize event handlers when you need to enforce filtering rules on real-time updates.

ChannelList also manages navigation: clicking a list item sets the active channel and renders Channel.

Basic Usage

ChannelList has no required props, but you should almost always pass filters, sort, and options.

Without filters, the query matches all channels in your app. That’s usually only useful in development.

At a minimum, the filter should include {members: { $in: [userID] }} .

const filters = { members: { $in: [ 'jimmy', 'buffet' ] } }
const sort = { last_message_at: -1 };
const options = { limit: 10 }

<Chat client={client}>
  <ChannelList filters={filters} sort={sort} options={options} />
  <Channel>
    <MessageList />
    <MessageInput />
  </Channel>
</Chat>

Set options.limit intentionally. The initial list size and subsequent pagination behavior depend on the query options you provide.

UI Customization

ChannelList UI is controlled by List (container) and Preview (item). If you don’t pass them, the defaults are ChannelListMessenger and ChannelPreviewMessenger.

If you enable showChannelSearch, ChannelList renders the Search UI above the list. Search customization now happens through WithComponents / ComponentContext, not through ChannelList props.

To customize the container or list item UI, provide component overrides. They receive the same props as the defaults.

const CustomListContainer = (props) => {
  // render custom list container here
};

const CustomListItem = (props) => {
  // render custom list item here
};

<Chat client={client}>
  <ChannelList List={CustomListContainer} Preview={CustomListItem} />
  <Channel>
    <MessageList />
    <MessageInput />
  </Channel>
</Chat>;

If Preview isn’t enough (for example, you need grouped sections), use renderChannels, which receives all loaded channels.

const renderChannels = (loadedChannels, ChannelPreview) => {
  const groups = groupBy(loadedChannels, 'some_custom_channel_data');
  return renderGroups(groups); // inside renderGroups you have have headings, etc...
}

<Chat client={client}>
  <ChannelList {/* other props */} renderChannels={renderChannels} />
  <Channel>
    <MessageList />
    <MessageInput />
  </Channel>
</Chat>;

Event Listeners

ChannelList registers event listeners on mount. Many handlers can be overridden via props.

Each handler receives the same arguments; you can read the event data and update list state as needed.

  • setChannels - state setter for the channels value which populates the list in the DOM
  • event - event object returned from each corresponding event listener
Event TypeDefault BehaviorCustom Handler
channel.deletedRemoves channel from listonChannelDeleted
channel.hiddenRemoves channel from listonChannelHidden
channel.truncatedUpdates the channelonChannelTruncated
channel.updatedUpdates the channelonChannelUpdated
channel.visibleAdds channel to listonChannelVisible
connection.recoveredForces a component renderN/A
message.newMoves channel to top of listonMessageNewHandler
notification.added_to_channelMoves channel to top of list and starts watchingonAddedToChannel
notification.message_newMoves channel to top of list and starts watchingonMessageNew
notification.removed_from_channelRemoves channel from listonRemovedFromChannel
user.presence.changedUpdates the channelN/A

Customizing Event Handlers

You can override the behavior for each event. Here’s an example that keeps the list limited to frozen channels.

const filters = {
  members: { $in: ['dan'] },
  frozen: true
}

<ChannelList filters={filters} />

The notification.message_new event occurs when a message is received on a channel that is not loaded but the current user is a member of. By default, this event queries the channel and adds it to the top of the list, regardless of filters. Thus, if a new message appears in an unfrozen channel of which the current user is a member, it will be added to the list. This may not be the desired behavior since the list is only supposed to show frozen channels.

Provide a custom onMessageNew prop to override this. It receives setChannels and the event so you can enforce your own filtering.

const filters = {
  members: { $in: ["dan"] },
  frozen: true,
};

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

  // If the channel isn't 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} onMessageNew={customOnMessageNew} />;

Other events can be overridden similarly.

Props

PropDescriptionType
allowNewMessagesFromUnfilteredChannelsWhen the client receives message.new, notification.message_new, and notification.added_to_channel events, the SDK automatically pushes that channel to the top of the list. If the channel does not currently exist in the list, it is taken from client.activeChannels and added. Set this prop to false to disable that behavior. Defaults to true.boolean
AvatarCustom UI component to display the user's avatar. Defaults to Avatar.component
channelRenderFilterFnOptional function to filter channels before loading them in the DOM. Avoid complex or async logic that delays ChannelList; prefer a pure function with array methods like filter, sort, or reduce.(channels: Channel[]) => Channel[]
customActiveChannelSets a channel with this ID as active and forces it to the top of the list.string
customQueryChannelsCustom function that handles channel pagination. It receives currentChannels, queryType, setChannels, and setHasNextPage, and is responsible for building query filters/sort/options, appending results, and updating pagination state.[CustomQueryChannelsFn](https://github.com/GetStream/stream-chat-react/blob/master/src/components/ChannelList/hooks/usePaginatedChannels.ts)
EmptyStateIndicatorCustom UI component for rendering an empty list. Defaults to EmptyStateIndicator.component
filtersObject containing channel query filters. See the query parameters docs for more information.object
getLatestMessagePreviewCustom function that generates the message preview in ChannelPreview.(channel: Channel, t: TranslationContextValue['t'], userLanguage: TranslationContextValue['userLanguage']) => string | JSX.Element
ListCustom UI component to display the container for the queried channels. Defaults to ChannelListMessenger.component
LoadingErrorIndicatorCustom UI component to display the loading error indicator. Defaults to NullComponent.component
LoadingIndicatorCustom UI component to display the loading state. Defaults to LoadingChannels.component
lockChannelOrderWhen true, channels do not dynamically resort by most recent message. Defaults to false.boolean
onAddedToChannelFunction overriding the default behavior when a user is added to a channel.function
onChannelDeletedFunction overriding the default behavior when a channel is deleted.function
onChannelHiddenFunction overriding the default behavior when a channel is hidden.function
onChannelTruncatedFunction overriding the default behavior when a channel is truncated.function
onChannelUpdatedFunction overriding the default behavior when a channel is updated.function
onChannelVisibleFunction overriding the default behavior when a channel becomes visible.function
onMessageNewFunction overriding the default behavior when a message is received on a channel that is not being watched.function
onMessageNewHandlerFunction overriding the default behavior when a message is received on a watched channel. Handles the message.new event.(setChannels: React.Dispatch<React.SetStateAction<Array<Channel>>>, event: Event) => void
onRemovedFromChannelFunction overriding the default behavior when a user is removed from a channel.function
optionsObject containing channel query options. See the query parameters docs for more information.object
PaginatorCustom UI component that handles channel pagination logic. Defaults to LoadMorePaginator.component
PreviewCustom UI component to display the channel preview in the list. Defaults to ChannelPreviewMessenger.component
recoveryThrottleIntervalMsInterval during which recovery channel-list queries are prevented to avoid unnecessary reloads during connection fluctuation. Recovery reloads the list from offset 0. The minimum interval is 2000ms. Defaults to 5000ms. This applies only if the StreamChat client's built-in channel-list recovery is disabled.number
renderChannelsFunction overriding the default render behavior so it is called instead of rendering Preview directly.function
sendChannelsToListIf true, sends the list's currently loaded channels to the List component as the loadedChannels prop. Defaults to false.boolean
setActiveChannelOnMountIf true, sets the most recent channel returned by the query as active on mount. If false, no channel is set active on mount. Defaults to true.boolean
showChannelSearchIf true, renders the Search UI above List. Customize the search surface through WithComponents / ComponentContext. Defaults to false.boolean
sortObject containing channel query sort parameters. See the query parameters docs for more information.object
watchersObject containing query parameters for fetching channel watchers.{ limit?: number; offset?: number }

Examples

Custom channel querying

customQueryChannels receives:

ParameterDescription
currentChannelsThe state of loaded Channel objects queried so far.
queryTypeWhether the state should reset to the first page ("reload") or append new channels.
setChannelsFunction that updates the channels state reflected in currentChannels.
setHasNextPageFlag setter indicating whether more items can be loaded from the API.

It is responsible for:

  1. building or providing query filters, sort, and options
  2. querying and appending channels to the current channels state
  3. updating the hasNext pagination flag after each query

The example below implements a custom query function that uses different filters sequentially once a preceding filter is exhausted:

import uniqBy from "lodash.uniqby";
import throttle from "lodash.throttle";
import { useCallback, useRef } from "react";
import {
  ChannelFilters,
  ChannelOptions,
  ChannelSort,
  StreamChat,
} from "stream-chat";
import { CustomQueryChannelParams, useChatContext } from "stream-chat-react";

const DEFAULT_PAGE_SIZE = 30 as const;

export const useCustomQueryChannels = () => {
  const { client } = useChatContext();
  const filters1: ChannelFilters = {
    member_count: { $gt: 10 },
    members: { $in: [client.user?.id || ""] },
    type: "messaging",
  };
  const filters2: ChannelFilters = {
    members: { $in: [client.user?.id || ""] },
    type: "messaging",
  };
  const options: ChannelOptions = { limit: 10, presence: true, state: true };
  const sort: ChannelSort = { last_message_at: -1, updated_at: -1 };

  const filtersArray = [filters1, filters2];
  const appliedFilterIndex = useRef(0);

  const customQueryChannels = useCallback(
    throttle(
      async ({
        currentChannels,
        queryType,
        setChannels,
        setHasNextPage,
      }: CustomQueryChannelParams) => {
        const offset = queryType === "reload" ? 0 : currentChannels.length;

        const newOptions = {
          limit: options.limit ?? DEFAULT_PAGE_SIZE,
          offset,
          ...options,
        };

        const filters = filtersArray[appliedFilterIndex.current];
        const channelQueryResponse = await client.queryChannels(
          filters,
          sort || {},
          newOptions,
        );

        const newChannels =
          queryType === "reload"
            ? channelQueryResponse
            : uniqBy([...currentChannels, ...channelQueryResponse], "cid");

        setChannels(newChannels);

        const lastPageForCurrentFilter =
          channelQueryResponse.length < newOptions.limit;
        const isLastPageForAllFilters =
          lastPageForCurrentFilter &&
          appliedFilterIndex.current === filtersArray.length - 1;

        setHasNextPage(!isLastPageForAllFilters);
        if (lastPageForCurrentFilter) {
          appliedFilterIndex.current += 1;
        }
      },
      500,
      { leading: true, trailing: false },
    ),
    [client, filtersArray],
  );

  return customQueryChannels;
};

It is recommended to control for duplicate requests by throttling the custom function calls.

Configure recovery throttling

The channel-list recovery mechanism described here is activated only if the StreamChat client's built-in channel-list recovery is disabled:

import { StreamChat } from 'stream-chat';
import { ChannelList, Chat } from 'stream-chat-react';

// ... get apiKey, filters, sort, options

const client = new StreamChat(apiKey, {recoverStateOnReconnect: false});
const App = () => (
    <Chat client={client} >
     {/** ... */}
        <ChannelList
        filters={filters}
        sort={sort}
        options={options}
        recoveryThrottleIntervalMs={3000}
        {/** other props... */}
      />
     {/** ... */}
    </Chat>
);