Info:
This is release candidate documentation for Stream Chat React SDK v15. For the latest stable version, see the latest version (v14).

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, from defaultChannelMembersModeViews;
  • 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. Takes onMemberSelect(member).
  • ChannelMembersAddView: the add mode. Takes the modeController and searchSource, a UserSearchSource that replaces the default user search.
  • DefaultChannelMembersHeaderActions: AddMembers, the button, and AddMembersMenu, the same action as a menu item.
  • useBaseChannelMembersHeaderActionSetFilter: the filter DefaultHeaderActions applies.

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
  • PinnedMessagesView lists pinned messages with their author and date, and a search field that filters them by text. searchSource replaces its search; its filters and sort are used as they are. Selecting a message opens the channel with openChannel from useWorkspaceNavigation(), jumps its message list to the message and closes the modal. While the channel has no pinned messages, it shows PinnedMessagesEmptyList; when a search matches none, "No messages found".
  • ChannelMediaView shows images and videos as a grid, with the video length, itemsPerPage at a time. Next loads more messages when the next page isn't loaded yet. Clicking an item opens it in the Gallery override, inside the Modal override.
  • ChannelFilesView lists 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 }
  • useChannelMembersSearch searches the channel's members with a ChannelMemberSearchSource. Until the first results arrive, displayedMembers holds the members the channel has loaded. resetMembersSearch() clears the query and reloads the list, and bumps searchInputResetKey, which resets a ChannelDetailSearchInput.
  • useChannelMediaSearch and useChannelFilesSearch search the channel's messages with image and video attachments, or file and audio ones, and turn them into items with the helpers below.
  • usePinnedMessagesSearch takes { searchSource }. Without one, it searches the channel's messages with pinned: 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.