Channel details UI

Channel details is a screen with the channel's profile, its members, its pinned messages, its photos, videos and files, and the actions the user can take on it, such as mute and leave. ChannelDetails renders it for the channel of the ChannelDetailsContextProvider around it. Use this page to open channel details from your navigator, to know what each part shows, and to replace parts. The props are in the ChannelDetails reference, and what each part calls in the state layer is on Channel details and members.

ChannelDetails renders these parts from top to bottom. The header stays at the top, and the other parts scroll in a ScrollView below it:

Part Shows Override key Explained in
1. The header the title, the back button, and in a group channel an Edit button ChannelDetailsNavHeader Parts
2. The profile the channel's avatar and name, and its member and online counts ChannelDetailsProfile Parts
3. The navigation the Pinned Messages, Photos & Videos and Files rows ChannelDetailsNavigationSection Parts
4. The members the number of members, an Add button, and the first members. Not in a direct message channel. ChannelDetailsMemberSection Parts
5. The actions the mute, leave, delete and block actions ChannelDetailsActionsSection Actions

Opening channel details

The SDK doesn't open channel details: it has no button that navigates to it. Give it a screen of your own, and navigate to that screen from your channel header, such as from the avatar in the header on Channel UI. ChannelDetails takes onBack, which its back button calls. The back button doesn't render without it.

src/ChannelDetailsScreen.tsx
import type { NativeStackScreenProps } from "@react-navigation/native-stack";
import {
  ChannelDetails,
  ChannelDetailsContextProvider,
  useChatContext,
} from "stream-chat-react-native";

type StackParamList = {
  ChannelDetails: { cid: string };
};

export const ChannelDetailsScreen = ({
  navigation,
  route,
}: NativeStackScreenProps<StackParamList, "ChannelDetails">) => {
  const { client } = useChatContext();
  const channel = client.channelManager.get(route.params.cid);

  if (!channel) return null;

  return (
    <ChannelDetailsContextProvider channel={channel}>
      <ChannelDetails onBack={() => navigation.goBack()} />
    </ChannelDetailsContextProvider>
  );
};

ChannelDetails reads the channel with useChannelDetailsContext(), so every part of it has to render inside a ChannelDetailsContextProvider. It doesn't need a Channel around it.

Parts

The header

The title is "Group Info" in a group channel and "Contact Info" in a direct message channel. The back button is on the start side, and the trailing side holds the Edit button in a group channel, when the user may update the channel. Edit opens ChannelEditDetailsForm, a form for the channel's name and image, on top of the screen.

The profile

The profile shows the channel's avatar and its name, with a mute icon after the name while the channel is muted. Below the name is a line with the number of members and how many are online in a group channel, and "Online" in a direct message channel while the other user is online.

The navigation

Three rows, in this order: Pinned Messages, Photos & Videos and Files. A row opens its list on top of the screen, in a full-screen modal: PinnedMessageList, MediaList and FileAttachmentList. Each list has an item key to replace one row of it: PinnedMessageItem, MediaItem and FileAttachmentItem.

ChannelDetailsNavigationSection takes getNavigationItems, which chooses the rows and sets what a row does when pressed, for example to push a screen of your own instead of opening the modal. See Custom navigation and Choosing the rows.

The members

The members section shows "8 members", an Add button, and the first five members, as ChannelMemberItems. A member shows their name, their role when they have one, and whether they're online or when they were last seen. When the channel has more than five members, View all opens the full list, ChannelMemberList, with a search field. Pressing a member opens a bottom sheet with that member's actions, ChannelMemberActionsSheet. The Add button opens ChannelAddMembersForm, a search for users to add.

The section isn't rendered in a direct message channel. A direct message channel here is one with exactly one member besides the current user.

What opens on top of the screen

The screen doesn't navigate on its own. What it opens, it opens on top of itself:

  • The three navigation rows open their lists in a React Native Modal, full screen.
  • The member list, the Edit form and the Add members form open in a Modal too. It's full screen on Android and a page sheet on iOS.
  • A member's actions and the channel image picker open as bottom sheets.

Overlays and bottom sheets lists each screen with its override key.

closeModals() from useChannelDetailsContext() closes every screen channel details opened, so call it before you navigate away from the screen. Selecting a pinned message or leaving the channel are two ways out of the screen that need your app. Moving between channels, threads and messages explains them.

Actions

The actions section lists what the user can do with the channel. mute and muteUser render as switches, and the other actions as rows: leave, delete, and in a direct message channel block. Each part of channel details that has actions lets you change them: the section's rows, and the actions in a member's sheet. Channel actions and Member actions cover how.

Leave, delete and block call onChannelDismiss on ChannelDetailsActionsSection once they succeed, so your app can close the channel's screens.

Notifications

ChannelDetails renders a notification list of its own for the notifications its actions publish, and each modal it opens renders one too. See Notifications UI.

Loading, empty and error states

The lists in channel details show a state of their own while they load, when they're empty, and when a query fails. List states UI lists each one.

Replacing parts

Register replacements with WithComponents. Overriding components explains how overrides apply.

Override key Replaces
ChannelDetailsContent The whole screen: the header and the sections. Gets onBack.
ChannelDetailsNavHeader The header. Gets title, onBack and action.
ChannelDetailsEditButton The Edit button and the form it opens
ChannelDetailsProfile The profile
ChannelDetailsNavigationSection The three rows. Gets getNavigationItems.
ChannelDetailsMemberSection The members section. Gets onViewAllMembersPress.
ChannelDetailsActionsSection, ChannelDetailsActionItem The actions section, and each row in it. Gets getChannelActionItems and onChannelDismiss.
ChannelMemberItem, ChannelMemberList, ChannelMemberActionsSheet A member row, the full member list, and the sheet with a member's actions
RoleItem, RoleList The roles a member row shows, and each role in it
ChannelAddMembersButton, ChannelAddMembersForm, ChannelAddMembersFormContent, ChannelAddMembersFormHeader The Add button and the form it opens, with the form's content and header
ChannelEditDetailsForm, ChannelEditDetailsFormContent, ChannelEditDetailsFormHeader, ChannelEditName, ChannelEditImageSheet The Edit form and its parts, and the sheet that picks a new image
PinnedMessageList, PinnedMessageItem The pinned messages list, and each row in it
MediaList, MediaItem The photos and videos grid, and each tile in it
FileAttachmentList, FileAttachmentItem The files list, and each row in it

Here is a ChannelDetailsWithCreator screen that keeps the default profile and shows who created the channel below it:

src/ChannelDetailsWithCreator.tsx
import { StyleSheet, Text, View } from "react-native";
import type { Channel as StreamChannel, ChannelStateData } from "stream-chat";
import {
  ChannelDetails,
  ChannelDetailsContextProvider,
  ChannelDetailsProfile,
  useChannelDetailsContext,
  useStateStore,
  WithComponents,
} from "stream-chat-react-native";
import type { ComponentOverrides } from "stream-chat-react-native";

const creatorSelector = ({ data }: ChannelStateData) => ({
  creator: data?.created_by?.name ?? data?.created_by?.id,
});

const ProfileWithCreator = () => {
  const { channel } = useChannelDetailsContext();
  const { creator } = useStateStore(channel.state, creatorSelector);

  return (
    <View>
      <ChannelDetailsProfile />
      {creator ? (
        <Text style={styles.creator}>Created by {creator}</Text>
      ) : null}
    </View>
  );
};

// Declared outside the component, so WithComponents gets the same object on every render.
const overrides: ComponentOverrides = {
  ChannelDetailsProfile: ProfileWithCreator,
};

export const ChannelDetailsWithCreator = ({
  channel,
  onBack,
}: {
  channel: StreamChannel;
  onBack: () => void;
}) => (
  <ChannelDetailsContextProvider channel={channel}>
    <WithComponents overrides={overrides}>
      <ChannelDetails onBack={onBack} />
    </WithComponents>
  </ChannelDetailsContextProvider>
);

const styles = StyleSheet.create({
  creator: { paddingBottom: 16, textAlign: "center" },
});

The creator is read from channel.state through useStateStore, so it follows the channel's data. The component reads the channel from useChannelDetailsContext(), because channel details can render outside a Channel.

Used in