const channels = await client.queryChannels(filters, sort, options);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.
Best Practices
- Always include a
membersfilter to avoid querying the entire app. - Keep
sortandoptionsconsistent with your UX (recency vs. priority lists). - Use
ChannelListfor navigation state instead of managing active channels manually. - Override
List/Previewonly 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>UI Customization
ChannelList UI is controlled by List (container) and Preview (item). If you don’t pass them, the defaults are
ChannelListMessenger
and ChannelPreviewMessenger.
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 thechannelsvalue which populates the list in the DOMevent- event object returned from each corresponding event listener
| Event Type | Default Behavior | Custom Handler |
|---|---|---|
channel.deleted | Removes channel from list | onChannelDeleted |
channel.hidden | Removes channel from list | onChannelHidden |
channel.truncated | Updates the channel | onChannelTruncated |
channel.updated | Updates the channel | onChannelUpdated |
channel.visible | Adds channel to list | onChannelVisible |
connection.recovered | Forces a component render | N/A |
message.new | Moves channel to top of list | onMessageNewHandler |
notification.added_to_channel | Moves channel to top of list and starts watching | onAddedToChannel |
notification.message_new | Moves channel to top of list and starts watching | onMessageNew |
notification.removed_from_channel | Removes channel from list | onRemovedFromChannel |
user.presence.changed | Updates the channel | N/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
additionalChannelSearchProps
Additional props to be passed to the underlying ChannelSearch component.
| Type |
|---|
| object |
allowNewMessagesFromUnfilteredChannels
When the client receives message.new, notification.message_new, and notification.added_to_channel events, we automatically push
that channel to the top of the list. If the channel doesn't currently exist in the list, we grab the channel from client.activeChannels
and push it to the top of the list. You can disable this behavior by setting this prop to false, which will prevent channels not in the
list from incrementing the list.
| Type | Default |
|---|---|
| boolean | true |
Avatar
Custom UI component to display the user's avatar.
| Type | Default |
|---|---|
| component | Avatar |
channelRenderFilterFn
Optional function to filter channels prior to loading in the DOM. Do not use any complex or async logic that would delay the
loading of the ChannelList. We recommend using a pure function with array methods like filter/sort/reduce.
| Type |
|---|
| (channels: Channel[]) => Channel[] |
ChannelSearch
Custom UI component to display search results.
| Type | Default |
|---|---|
| component | ChannelSearch |
customActiveChannel
Set a channel (with this ID) to active and force it to move to the top of the list.
| Type |
|---|
| string |
customQueryChannels
Custom function that handles the channel pagination.
Takes parameters:
| Parameter | Description |
|---|---|
currentChannels | The state of loaded Channel objects queried thus far. Has to be set with setChannels (see below). |
queryType | A string indicating, whether the channels state has to be reset to the first page ('reload') or newly queried channels should be appended to the currentChannels. |
setChannels | Function that allows us to set the channels state reflected in currentChannels. |
setHasNextPage | Flag indicating whether there are more items to be loaded from the API. Should be infered from the comparison of the query result length and the query options limit. |
The function has to:
- build / provide own query filters, sort and options parameters
- query and append channels to the current channels state
- update the
hasNextpagination flag after each query withsetChannelsfunction
An 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.
EmptyStateIndicator
Custom UI component for rendering an empty list.
| Type | Default |
|---|---|
| component | EmptyStateIndicator |
filters
An object containing channel query filters, check our query parameters docs for more information.
| Type |
|---|
| object |
getLatestMessagePreview
Custom function that generates the message preview in ChannelPreview component.
| Type |
|---|
(channel: Channel, t: TranslationContextValue['t'], userLanguage: TranslationContextValue['userLanguage']) => string | JSX.Element |
List
Custom UI component to display the container for the queried channels.
| Type | Default |
|---|---|
| component | ChannelListMessenger |
LoadingErrorIndicator
Custom UI component to display the loading error indicator.
| Type | Default |
|---|---|
| component | NullComponent |
LoadingIndicator
Custom UI component to display the loading state.
| Type | Default |
|---|---|
| component | LoadingChannels |
lockChannelOrder
When true, channels won't dynamically sort by most recent message.
| Type | Default |
|---|---|
| boolean | false |
onAddedToChannel
Function to override the default behavior when a user is added to a channel.
| Type |
|---|
| function |
onChannelDeleted
Function to override the default behavior when a channel is deleted.
| Type |
|---|
| function |
onChannelHidden
Function to override the default behavior when a channel is hidden.
| Type |
|---|
| function |
onChannelTruncated
Function to override the default behavior when a channel is truncated.
| Type |
|---|
| function |
onChannelUpdated
Function to override the default behavior when a channel is updated.
| Type |
|---|
| function |
onChannelVisible
Function to override the default channel visible behavior.
| Type |
|---|
| function |
onMessageNew
Function to override the default behavior when a message is received on a channel not being watched.
| Type |
|---|
| function |
onMessageNewHandler
Function to override the default behavior when a message is received on a channel being watched. Handles message.new event.
| Type |
|---|
(setChannels: React.Dispatch<React.SetStateAction<Array<Channel>>>, event: Event) => void |
onRemovedFromChannel
Function to override the default behavior when a user gets removed from a channel.
| Type |
|---|
| function |
options
An object containing channel query options, check our query parameters docs for more information.
| Type |
|---|
| object |
Paginator
Custom UI component to handle channel pagination logic.
| Type | Default |
|---|---|
| component | LoadMorePaginator |
Preview
Custom UI component to display the channel preview in the list.
| Type | Default |
|---|---|
| component | ChannelPreviewMessenger |
recoveryThrottleIntervalMs
Custom interval during which the recovery channel list queries will be prevented. This is to avoid firing unnecessary queries during internet connection fluctuation. Recovery channel query is triggered upon internet connection recovery and leads to complete channel list reload with pagination offset 0. The minimum throttle interval is 2000ms. The default throttle interval is 5000ms.
The channel list recovery mechanism described here (applying recoveryThrottleIntervalMs) is activated only if the StreamChat client's channel list recovery mechanism is disabled. The StreamChat recovery mechanism can be disabled when initiating the client instance through the options parameter:
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>
);| Type | Default |
|---|---|
| number | 5000 |
renderChannels
Function to override the default behavior when rendering channels, so this function is called instead of rendering the Preview directly.
| Type |
|---|
| function |
sendChannelsToList
If true, sends the list's currently loaded channels to the List component as the loadedChannels prop.
| Type | Default |
|---|---|
| boolean | false |
setActiveChannelOnMount
If true, sets the most recent channel received from the query as active on component mount. If set to false no channel is set as active on mount.
| Type | Default |
|---|---|
| boolean | true |
showChannelSearch
If true, renders the ChannelSearch component above the List component.
| Type | Default |
|---|---|
| boolean | false |
sort
An object containing channel query sort parameters. Check our query parameters docs for more information.
| Type |
|---|
| object |
watchers
An object containing query parameters for fetching channel watchers.
| Type |
|---|
{ limit?: number; offset?: number } |
- Best Practices
- Basic Usage
- UI Customization
- Event Listeners
- Customizing Event Handlers
- Props
- additionalChannelSearchProps
- allowNewMessagesFromUnfilteredChannels
- Avatar
- channelRenderFilterFn
- ChannelSearch
- customActiveChannel
- customQueryChannels
- EmptyStateIndicator
- filters
- getLatestMessagePreview
- List
- LoadingErrorIndicator
- LoadingIndicator
- lockChannelOrder
- onAddedToChannel
- onChannelDeleted
- onChannelHidden
- onChannelTruncated
- onChannelUpdated
- onChannelVisible
- onMessageNew
- onMessageNewHandler
- onRemovedFromChannel
- options
- Paginator
- Preview
- recoveryThrottleIntervalMs
- renderChannels
- sendChannelsToList
- setActiveChannelOnMount
- showChannelSearch
- sort
- watchers