import { Channel, Chat, MessageInput, MessageList } from "stream-chat-react";
const App = () => (
<Chat client={client}>
<Channel channel={channel}>
<MessageList />
<MessageInput />
</Channel>
</Chat>
);Channel
The Channel component wraps the logic, state, and UI for a single chat channel. It provides four contexts to its children:
ChannelStateContext- stateful data (ex:messagesormembers)ChannelActionContext- action handlers (ex:sendMessageoropenThread)ComponentContext- SDK UI overrides registered withWithComponentsTypingContext- object of currently typing users (i.e.,typing)
Best Practices
- Use
ChannelListto manage active channels unless you need custom switching logic. - Pass the
channelprop only when you control selection; avoid it when usingChannelList. - Register SDK UI overrides with
WithComponents, and keepChannelfocused on channel behavior and data loading. - Set
channelQueryOptionsintentionally if your app depends on specific initial limits. - Access context via hooks inside
Channelchildren 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 achannelprop.ChannelListsets the active channel. - If you don’t use
ChannelList, you must pass thechannelprop.
Example 1 - without ChannelList
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
| Prop | Description | Type |
|---|---|---|
activeUnreadHandler | Custom handler that runs when the active channel has unread messages and the app is running in another browser tab. | (unread: number, documentTitle: string) => void |
allowConcurrentAudioPlayback | Allows multiple audio players to play at the same time. Disabled by default. | boolean |
channel | The 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 |
channelQueryOptions | Optional 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 |
doDeleteMessageRequest | Custom action handler overriding the default client.deleteMessage(message.id) function. | (message: LocalMessage, options?: DeleteMessageOptions) => Promise<MessageResponse> |
doMarkReadRequest | Custom 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 |
doSendMessageRequest | Custom action handler overriding the default channel.sendMessage request. | (channel: StreamChannel, message: Message, options?: SendMessageOptions) => Promise<SendMessageAPIResponse> | void |
doUpdateMessageRequest | Custom action handler overriding the default client.updateMessage request. | (cid: string, updatedMessage: LocalMessage | MessageResponse, options?: UpdateMessageOptions) => Promise<UpdateMessageAPIResponse> |
EmptyPlaceholder | Custom 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 |
giphyVersion | The Giphy image version to render. See the keys of the Image Object for supported values. Defaults to fixed_height. | GiphyVersions |
imageAttachmentSizeHandler | Custom function to provide size configuration for image attachments. | ImageAttachmentSizeHandler |
initializeOnMount | Prevents 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 |
markReadOnMount | Controls whether the active channel is marked read when mounted. Defaults to true. | boolean |
onMentionsClick | Custom action handler to run when an @mention in a message is clicked. | OnMentionAction |
onMentionsHover | Custom action handler to run when an @mention in a message is hovered. | OnMentionAction |
shouldGenerateVideoThumbnail | Turns video thumbnail generation on or off for video attachments. | boolean |
skipMessageDataMemoization | If true, skips the message-data string comparison used to memoize current channel messages. This can help with channels that render thousands of messages. | boolean |
videoAttachmentSizeHandler | Custom 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} />
</>
);