Channel Detail Views
The views are the panels of ChannelDetail's five default sections. Each one is a section's SectionContent: it gets the current layout and reads the channel with useChannelDetailContext(). Channel details and members explains what each one reads and calls.
| View | Section id | Shows |
|---|---|---|
ChannelManagementView |
channel-info |
The channel's avatar, name and online status, an edit form, and the channel actions |
ChannelMembersView |
channel-members |
The members, adding members, and one member's details |
PinnedMessagesView |
pinned-messages |
The pinned messages, with a search field |
ChannelMediaView |
channel-media |
A grid of the images and videos shared in the channel |
ChannelFilesView |
channel-files |
The files and audio shared in the channel, by month |
All of them come from stream-chat-react/channel-detail. To pass props to a view, replace the section's SectionContent with a component that renders the view with them, as in Choose or reorder sections.
ChannelManagementView
The Channel info section. It shows the channel's avatar and name, whether it's pinned or muted, and its online status. Edit, shown with the update-channel capability, switches to a form that changes the name and the image. Below are the channel actions.
It reads the online status from the channel in context, so render ChannelDetail inside a Channel for the same channel; without one it throws.
| Prop | Type | Default | Description |
|---|---|---|---|
layout |
SectionNavigatorLayout |
from the navigator | The current layout. |
channelManagementActionSet |
ChannelManagementActionItem[] |
defaultChannelManagementActionSet |
The channel actions, in order. useBaseChannelManagementActionSetFilter removes the ones that don't apply. |
uploadImage |
(file: File) => Promise<string> | string |
Channel.uploadImage() |
Uploads a new channel image and returns its URL. |
ViewModeComponent |
React.ComponentType<ChannelManagementInfoBodyProps> |
ChannelManagementInfoBody |
The body outside edit mode. Gets the filtered actions. |
EditModeComponent |
React.ComponentType<ChannelManagementEditBodyProps> |
ChannelManagementEditBody |
The edit form. Gets uploadImage. |
Saving calls Channel.updatePartial() with custom.name and custom.image, and publishes a "Changes saved" or "Failed to save changes" notification.
An action is { type, Component }. The types of the defaults are muteChannel, muteUser, blockUser, leaveChannel and deleteChat, and their components are in DefaultChannelManagementActions: MuteChannel, MuteUser, BlockUser, LeaveChannel and DeleteChat. Leaving, deleting and blocking ask for confirmation first.
import {
DefaultChannelManagementActions,
type ChannelManagementActionItem,
} from "stream-chat-react/channel-detail";
// Only muting and leaving, in this order.
export const channelManagementActionSet: ChannelManagementActionItem[] = [
{
Component: DefaultChannelManagementActions.MuteChannel,
type: "muteChannel",
},
{
Component: DefaultChannelManagementActions.LeaveChannel,
type: "leaveChannel",
},
];Channel details actions shows how to add your own actions and which capabilities the filter checks.
ChannelMembersView
The Members section. It has three modes:
browse, the default: the member list, with a search field and the header actions;add: a search for users to add, fromdefaultChannelMembersModeViews;memberDetail: one member's details, opened by clicking a member.
browse and memberDetail are built in. Every other mode, add included, is looked up in the modeViews registry.
| Prop | Type | Default | Description |
|---|---|---|---|
layout |
SectionNavigatorLayout |
from the navigator | The current layout. Passed on to ChannelMemberDetail. |
headerActionSet |
ChannelMembersHeaderActionItem[] |
defaultChannelMembersHeaderActionSet |
The actions in the header of the member list. |
HeaderActions |
React.ComponentType<ChannelMembersHeaderActionsProps> |
DefaultHeaderActions |
Renders the header actions: quick ones as buttons, menu ones in a menu. |
HeaderActionsMenuTrigger |
React.ComponentType<ChannelMembersHeaderActionsMenuTriggerProps> |
DefaultHeaderActionsMenuTrigger |
The button that opens the menu of header actions. |
modeViews |
ChannelMembersViewModes |
defaultChannelMembersModeViews |
Your modes, merged over the default ones. A mode with the same key replaces it. |
A header action is { type, component, placement, filter? }. Its component gets the modeController and, in the menu, closeMenu. placement is "quick" or "menu". The default set has one action, addMembers, a quick button that opens the add mode and needs update-channel-members. An action of your own can carry filter({ channel }) to decide whether it shows.
A mode is { Title, Body }, keyed by the name passed to modeController.setMode(). Title is a component, so it can read context. Body gets the modeController, { mode, setMode }. A mode other than browse shows a back button that returns to the list:
import {
ChannelMembersView,
useChannelDetailContext,
} from "stream-chat-react/channel-detail";
import type {
ChannelMembersModeViewProps,
ChannelMembersViewModes,
SectionNavigatorSectionContentProps,
} from "stream-chat-react/channel-detail";
const InviteLinkBody = ({ modeController }: ChannelMembersModeViewProps) => {
const { channel } = useChannelDetailContext();
return (
<div>
<code>{`${window.location.origin}/join/${channel.id}`}</code>
<button onClick={() => modeController.setMode("browse")} type="button">
Done
</button>
</div>
);
};
const modeViews: ChannelMembersViewModes = {
invite: { Body: InviteLinkBody, Title: () => <>Invite link</> },
};
// Use it as the SectionContent of the "channel-members" section.
export const MembersSection = (props: SectionNavigatorSectionContentProps) => (
<ChannelMembersView {...props} modeViews={modeViews} />
);A header action calls modeController.setMode("invite") to open it.
The parts of the view are exported too:
ChannelMembersBrowseView: the member list. TakesonMemberSelect(member).ChannelMembersAddView: theaddmode. Takes themodeControllerandsearchSource, aUserSearchSourcethat replaces the default user search.DefaultChannelMembersHeaderActions:AddMembers, the button, andAddMembersMenu, the same action as a menu item.useBaseChannelMembersHeaderActionSetFilter: the filterDefaultHeaderActionsapplies.
ChannelMemberDetail
One member's details: their avatar, name and presence, and the member actions.
| Prop | Type | Default | Description |
|---|---|---|---|
member |
ChannelMemberResponse |
required | The member to show. |
channelMemberActionSet |
ChannelMemberActionItem[] |
defaultChannelMemberActionSet |
The member actions, in order. useBaseChannelMemberActionSetFilter removes the ones that don't apply. |
onBack |
() => void |
none | Shows a back button that calls it. |
layout |
SectionNavigatorLayout |
required | The current layout. The type requires it, but the view doesn't read it. |
An action is { type, Component }. The defaults are sendMessage, muteUser, blockUser and removeUser, with their components in DefaultChannelMemberActions: SendDirectMessage, MuteUser, BlockUser and RemoveUser. An action's component reads the member with useChannelMemberActionContext(), which returns { member, memberDisplayName, targetUserId }.
ChannelMembersView renders ChannelMemberDetail without a channelMemberActionSet, so the member list always opens the default actions. Your set applies only to a ChannelMemberDetail you render yourself.
PinnedMessagesView, ChannelMediaView, ChannelFilesView
These three views list messages from a search limited to the channel. The pinned messages and files lists load the next page as the user scrolls; the media grid has Previous and Next buttons.
| View | Props | Search hook |
|---|---|---|
PinnedMessagesView |
layout, searchSource?: MessageSearchSource |
usePinnedMessagesSearch |
ChannelMediaView |
layout, itemsPerPage?: number, 30 by default |
useChannelMediaSearch |
ChannelFilesView |
layout |
useChannelFilesSearch |
PinnedMessagesViewlists pinned messages with their author and date, and a search field that filters them by text.searchSourcereplaces its search; its filters and sort are used as they are. Selecting a message opens the channel withopenChannelfromuseWorkspaceNavigation(), jumps its message list to the message and closes the modal. While the channel has no pinned messages, it showsPinnedMessagesEmptyList; when a search matches none, "No messages found".ChannelMediaViewshows images and videos as a grid, with the video length,itemsPerPageat a time. Next loads more messages when the next page isn't loaded yet. Clicking an item opens it in theGalleryoverride, inside theModaloverride.ChannelFilesViewlists files and audio grouped by month, newest first. A file with a URL links to it.
To change one of them, replace it with a component of your own built on its search hook.
Search & pagination hooks
Each list view loads through a hook that builds a search source for the channel. Use them to build your own list. They need ChannelDetail or a ChannelDetailProvider around them, and throw outside both.
| Hook | Returns |
|---|---|
useChannelMembersSearch |
{ displayedMembers, handleSearchChange, hasMembers, membersSearchSource, resetMembersSearch, searchInputResetKey } |
useChannelMediaSearch |
{ channelMediaSearchSource, hasNext, hasResultsLoaded, isLoading, mediaItems } |
useChannelFilesSearch |
{ channelFilesSearchSource, fileItems, groupCounts, hasResultsLoaded, isLoading, sections } |
usePinnedMessagesSearch |
{ displayedMessages, handleSearchChange, hasPinnedMessages, hasSearchResultsLoaded, pinnedMessagesSearchSource } |
useChannelMembersSearchsearches the channel's members with aChannelMemberSearchSource. Until the first results arrive,displayedMembersholds the members the channel has loaded.resetMembersSearch()clears the query and reloads the list, and bumpssearchInputResetKey, which resets aChannelDetailSearchInput.useChannelMediaSearchanduseChannelFilesSearchsearch the channel's messages with image and video attachments, or file and audio ones, and turn them into items with the helpers below.usePinnedMessagesSearchtakes{ searchSource }. Without one, it searches the channel's messages withpinned: true, matching text with$autocomplete. It loads only while the channel has pinned messages.
import {
ChannelDetailListLoadingIndicator,
useChannelMediaSearch,
} from "stream-chat-react/channel-detail";
export const MediaThumbnails = () => {
const {
channelMediaSearchSource,
hasNext,
hasResultsLoaded,
isLoading,
mediaItems,
} = useChannelMediaSearch();
if (isLoading && !hasResultsLoaded) return <span>Loading…</span>;
return (
<>
<ul className="media-thumbnails">
{mediaItems.map((item) => (
<li key={item.id}>
{item.type} from {item.user?.name ?? "unknown"}
</li>
))}
</ul>
{hasNext && (
// Without a new query, search() loads the next page.
<button onClick={() => channelMediaSearchSource.search()} type="button">
Load more
</button>
)}
<ChannelDetailListLoadingIndicator
searchSource={channelMediaSearchSource}
/>
</>
);
};Search sources explains how a source loads its pages. The hooks' signatures are on Loading data hooks.
Media & files data transforms
The media and files views turn messages into items with two helpers. Use them to build the same items from messages you have:
| Helper | Signature | Returns |
|---|---|---|
toChannelMediaItems |
(messages: Array<MessageResponse | LocalMessage>) => ChannelMediaItem[] |
One item per image or video attachment that has something to show: { galleryItem, id, type, durationSeconds?, user? }. galleryItem is what the Gallery takes, with the attachment's message as message. |
toChannelFileSections |
(messages: Array<MessageResponse | LocalMessage>) => ChannelFileSections |
{ groupCounts, items, sections }: the file and audio attachments as items, grouped by month, newest first. sections[i] is a month, { key, timestamp? }, and groupCounts[i] how many of the items belong to it. |
toChannelFileSections returns the shape GroupedVirtuoso takes. Each item is { attachment, id, createdAt? }, with the attachment as it came. A section's key is YYYY-MM, or unknown for messages without a date.
import type { LocalMessage } from "stream-chat";
import { toChannelFileSections } from "stream-chat-react/channel-detail";
export const FilesByMonth = ({ messages }: { messages: LocalMessage[] }) => {
const { groupCounts, items, sections } = toChannelFileSections(messages);
let start = 0;
return (
<>
{sections.map((section, index) => {
const sectionItems = items.slice(start, start + groupCounts[index]);
start += groupCounts[index];
return (
<section key={section.key}>
<h3>{section.key}</h3>
<ul>
{sectionItems.map((item) => (
<li key={item.id}>{item.attachment.title}</li>
))}
</ul>
</section>
);
})}
</>
);
};The attachment types each helper keeps are exported as MEDIA_ATTACHMENT_TYPES, ["image", "video"], and FILE_ATTACHMENT_TYPES, ["file", "audio"]. The types are ChannelMediaItem, ChannelMediaItemType, ChannelFileItem, ChannelFileSection, ChannelFileSections and ChannelFileAttachmentType.