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

Channel

The Channel component wraps the logic, state, and UI for a single chat channel. It provides four contexts to its children:

Best Practices

  • Use ChannelList to manage active channels unless you need custom switching logic.
  • Pass the channel prop only when you control selection; avoid it when using ChannelList.
  • Register SDK UI overrides with WithComponents, and keep Channel focused on channel behavior and data loading.
  • Set channelQueryOptions intentionally if your app depends on specific initial limits.
  • Access context via hooks inside Channel children to avoid stale or missing data.

Channel renders a single channel object. For details about channel objects, see the JavaScript docs.

Basic Usage

Channel doesn’t render UI on its own. You can use it with or without ChannelList:

  • If you use ChannelList, don’t pass a channel prop. ChannelList sets the active channel.
  • If you don’t use ChannelList, you must pass the channel prop.

Example 1 - without ChannelList

import { Channel, Chat, MessageInput, MessageList } from "stream-chat-react";

const App = () => (
  <Chat client={client}>
    <Channel channel={channel}>
      <MessageList />
      <MessageInput />
    </Channel>
  </Chat>
);

Example 2 - with ChannelList

import {
  Channel,
  ChannelList,
  Chat,
  MessageInput,
  MessageList,
} from "stream-chat-react";

const App = () => (
  <Chat client={client}>
    <ChannelList />
    <Channel>
      <MessageList />
      <MessageInput />
    </Channel>
  </Chat>
);

Any child of Channel can access these contexts via hooks:

import {
  useChannelActionContext,
  useChannelStateContext,
  useComponentContext,
  useTypingContext,
} from "stream-chat-react";

const CustomChannelChild = () => {
  const { messages } = useChannelStateContext();
  const { sendMessage } = useChannelActionContext();
  const { Avatar } = useComponentContext();
  const { typing } = useTypingContext();

  return (
    <div>
      <span>{messages.length} messages loaded</span>
      <span>{Avatar ? "avatar override available" : "default avatar"}</span>
      <span>{Object.keys(typing).length} people typing</span>
    </div>
  );
};

Registering Custom Components

Use WithComponents to replace SDK UI within a Channel subtree. Channel itself owns channel behavior, data loading, and action hooks. The SDK UI override surface is exposed through ComponentContext.

import {
  Channel,
  ChannelHeader,
  ChannelList,
  Chat,
  MessageInput,
  MessageList,
  Thread,
  Window,
  WithComponents,
} from "stream-chat-react";
import { CustomTooltip } from "../Tooltip/CustomTooltip";

const CustomAvatar = ({ imageUrl, userName }) => (
  <>
    <CustomTooltip>{userName}</CustomTooltip>
    <div className="avatar-image">
      <img alt={userName} src={imageUrl} />
    </div>
  </>
);

const App = () => (
  <Chat client={client}>
    <ChannelList />
    <WithComponents overrides={{ Avatar: CustomAvatar }}>
      <Channel>
        <Window>
          <ChannelHeader />
          <MessageList />
          <MessageInput />
        </Window>
        <Thread />
      </Channel>
    </WithComponents>
  </Chat>
);

You can still customize components that expose local props directly. For example, MessageInput still accepts an Input prop for per-instance customization:

<Channel>
  <MessageList />
  <MessageInput Input={CustomMessageComposer} />
</Channel>

Props

PropDescriptionType
activeUnreadHandlerCustom handler that runs when the active channel has unread messages and the app is running in another browser tab.(unread: number, documentTitle: string) => void
allowConcurrentAudioPlaybackAllows multiple audio players to play at the same time. Disabled by default.boolean
channelThe active StreamChat channel instance loaded into Channel and referenced by its children. Do not provide this prop if you are using ChannelList, because ChannelList handles channel selection.object
channelQueryOptionsOptional configuration for the initial channel query. Applied only if channel.initialized is false. If the channel instance is already initialized, the query is skipped and these options are not applied.ChannelQueryOptions
doDeleteMessageRequestCustom action handler overriding the default client.deleteMessage(message.id) function.(message: LocalMessage, options?: DeleteMessageOptions) => Promise<MessageResponse>
doMarkReadRequestCustom action handler overriding the default channel.markRead request. Receives the current channel and an optional unread-state updater.(channel: StreamChannel, setChannelUnreadUiState?: (state: ChannelUnreadUiState) => void) => Promise<EventAPIResponse> | void
doSendMessageRequestCustom action handler overriding the default channel.sendMessage request.(channel: StreamChannel, message: Message, options?: SendMessageOptions) => Promise<SendMessageAPIResponse> | void
doUpdateMessageRequestCustom action handler overriding the default client.updateMessage request.(cid: string, updatedMessage: LocalMessage | MessageResponse, options?: UpdateMessageOptions) => Promise<UpdateMessageAPIResponse>
EmptyPlaceholderCustom React element rendered when no active channel is set. By default, Channel renders the current EmptyStateIndicator. Pass null to suppress empty-state rendering entirely.ReactElement | null
giphyVersionThe Giphy image version to render. See the keys of the Image Object for supported values. Defaults to fixed_height.GiphyVersions
imageAttachmentSizeHandlerCustom function to provide size configuration for image attachments.ImageAttachmentSizeHandler
initializeOnMountPrevents the initial channel.watch() call when mounting the component. When false, channel data is not fetched and WebSocket events are not subscribed until you initialize the channel yourself.boolean
markReadOnMountControls whether the active channel is marked read when mounted. Defaults to true.boolean
onMentionsClickCustom action handler to run when an @mention in a message is clicked.OnMentionAction
onMentionsHoverCustom action handler to run when an @mention in a message is hovered.OnMentionAction
shouldGenerateVideoThumbnailTurns video thumbnail generation on or off for video attachments.boolean
skipMessageDataMemoizationIf true, skips the message-data string comparison used to memoize current channel messages. This can help with channels that render thousands of messages.boolean
videoAttachmentSizeHandlerCustom function to provide size configuration for video attachments.VideoAttachmentSizeHandler

Examples

Create and pass a channel manually

const channel = client.channel("messaging", {
  members: ["nate", "roy"],
});

Provide custom initial query options

import type { ChannelQueryOptions } from "stream-chat";
import { Channel, useChatContext } from "stream-chat-react";

const channelQueryOptions: ChannelQueryOptions = {
  messages: { limit: 20 },
  watchers: { limit: 10 },
};

type ChannelRendererProps = {
  id: string;
  type: string;
};

const ChannelRenderer = ({ id, type }: ChannelRendererProps) => {
  const { client } = useChatContext();

  return (
    <Channel
      channel={client.channel(type, id)}
      channelQueryOptions={channelQueryOptions}
    >
      {/* Channel children */}
    </Channel>
  );
};

Customize delete-message requests

import type {
  DeleteMessageOptions,
  LocalMessage,
  MessageResponse,
} from "stream-chat";
import { Channel } from "stream-chat-react";

const doDeleteMessageRequest = async (
  message: LocalMessage,
  options?: DeleteMessageOptions,
): Promise<MessageResponse> => {
  if (message.parent_id) {
    // custom reply deletion flow
  } else {
    // custom main-list deletion flow
  }

  return client.deleteMessage(message.id, options);
};

const App = () => (
  <Channel doDeleteMessageRequest={doDeleteMessageRequest}>{/* ... */}</Channel>
);

Render a custom empty placeholder

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

const App = () => (
  <>
    <Channel EmptyPlaceholder={<div>Select a conversation</div>} />
    <Channel EmptyPlaceholder={null} />
  </>
);