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

ChannelListContext

The context value is provided by ChannelListContextProvider, which wraps the content rendered by ChannelList. It exposes API used by the default and custom components. In v14, SDK-owned channel list UI customization mainly flows through WithComponents / ComponentContext:

  • ChannelListUI - renders the list container, loading state, and error state
  • ChannelListItemUI - renders the information for one channel row
  • Avatar - component used to display the channel image inside the default row UI
  • LoadingErrorIndicator - rendered when the channels query fails
  • LoadingIndicator - rendered during the channels query
  • Search - renders the search input and results when showChannelSearch is enabled
  • EmptyStateIndicator - still provided directly through ChannelList
  • Paginator - still provided directly through ChannelList

Best Practices

  • Read from context instead of passing channel list state down manually.
  • Defer setChannels until the initial query completes to avoid overwrites.
  • Keep custom list components aligned with the default loading/error states.
  • Use Paginator for infinite scroll instead of DIY pagination.
  • Register SDK-owned channel list UI overrides through WithComponents.
  • Gate expensive logic to avoid blocking list rendering.

Basic Usage

Access the API from ChannelListContext with our custom hook:

import { useChannelListContext } from "stream-chat-react";

export const CustomComponent = () => {
  const { channels, setChannels } = useChannelListContext();
  // component logic ...
  return {
    /* rendered elements */
  };
};

Values

ValueDescriptionType
channelsState representing the array of loaded channels. By default, the channels query is executed by ChannelList.Channel[]
setChannelsSets the list of channels rendered by ChannelList. Be careful when calling this early: the initial ChannelList query overwrites the whole channels state. A common pattern is to wait for the channels.queried event before calling setChannels.Dispatch<SetStateAction<Channel[]>>

Examples

Set the active channel from the URL

The example below waits for the first page, then falls back to getChannel() if the channel is not in that page:

import { useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import type { Event } from 'stream-chat';
import {
  ChannelList,
  ChannelListUI,
  type ChannelListUIProps,
  WithComponents,
  getChannel,
  useChannelListContext,
  useChatContext,
} from 'stream-chat-react';

const DEFAULT_CHANNEL_ID = 'general';
const DEFAULT_CHANNEL_TYPE = 'messaging';

const CustomChannelListUI = (props: ChannelListUIProps) => {
  const { channelId } = useParams();
  const navigate = useNavigate();
  const { client, channel, setActiveChannel } = useChatContext();
  const { setChannels } = useChannelListContext();

  useEffect(() => {
    if (!channelId) return navigate(`/${DEFAULT_CHANNEL_ID}`);

    if (channel?.id === channelId || !client) return;

    let subscription: { unsubscribe: () => void } | undefined;
    if(!channel?.id || channel?.id !== channelId) {
      subscription = client.on('channels.queried', (event: Event) => {
        const loadedChannelData = event.queriedChannels?.channels.find((response) => response.channel.id === channelId);

        if (loadedChannelData) {
          setActiveChannel(client.channel( DEFAULT_CHANNEL_TYPE, channelId));
          subscription?.unsubscribe();
          return;
        }

        return getChannel({client, id: channelId, type: DEFAULT_CHANNEL_TYPE}).then((newActiveChannel) => {
          setActiveChannel(newActiveChannel);
          setChannels((channels) => {
            return ([newActiveChannel, ...channels.filter((ch) => ch.data?.cid !== newActiveChannel.data?.cid)]);
          });
        });
      });
    }

    return () => {
      subscription?.unsubscribe();
    };
  }, [channel?.id, channelId, setChannels, client, navigate, setActiveChannel]);

  return <ChannelListUI {...props} />;
};

const Sidebar = () => {
  return (
    <WithComponents overrides={{ ChannelListUI: CustomChannelListUI }}>
      <ChannelList
        {/* some props */}
        {/* setting active channel will be performed inside the custom ChannelListUI component */}
        setActiveChannelOnMount={false}
        {/* some props */}
      />
    </WithComponents>
  );
};