ChatView components
ChatView is the SDK's layout. It decides where an opened channel or thread goes, and the components on this page render what it placed. Workspace with ChatView explains how they work together.
Everything on this page imports from stream-chat-react/slot-layout.
ChatView
The root of the layout. It holds the layout's state, provides workspace navigation to the components inside it, and renders the shown view. What it renders depends on its props:
- With
views: itschildren, then the shown view's content. - With
layout="nav-rail-entity-list-workspace"and noviews: the built-in workspace layout, the view selector in a rail, then every slot of the shown view rendered throughslotRenderers. Itschildrenaren't rendered. - With neither: its
children.
Its root element has the classes str-chat, the theme from Chat, and str-chat__chat-view.
| Prop | Type | Default | Description |
|---|---|---|---|
children |
ReactNode |
With views, rendered in every view, before the shown view's content: put components that must always run here, such as DisposedChannelRelease. With layout and no views, not rendered. With neither, the whole content. |
|
deriveWorkspaceNavigation |
DeriveWorkspaceNavigation |
Replaces members of the navigation that ChatView provides. See Overriding navigation. Keep it stable. |
|
dialogManagerId |
string |
a local manager | Registers the dialog manager ChatView hosts inside its root under this id. Dialogs opened by the views' content render there, inside .str-chat. See Dialog managers. |
duplicateEntityPolicy |
"allow" | "move" | "reject" |
"allow" |
What happens when a channel or thread is opened into a slot while another slot holds it. See Duplicates. A new value starts the layout again. |
entityInferrers |
ChatViewEntityInferer[] |
No effect: ChatView accepts it, but nothing reads it. |
|
layout |
"nav-rail-entity-list-workspace" |
Renders the built-in workspace layout. Ignored when views is passed. |
|
layoutController |
LayoutController |
one ChatView creates |
A layout controller to use instead. layouts, duplicateEntityPolicy and resolveDuplicateEntity set up the controller ChatView creates, so they have no effect with this prop. The LayoutController class isn't exported, only its type. |
layouts |
LayoutDescriptor[] |
one slot per view, slot1, holding its list |
Each view's slots, in order, and what they hold at the start. See LayoutDescriptor. Without views or layout, the default is one empty slot1, in the channels view only. A new array starts the layout again. |
resolveDuplicateEntity |
ResolveDuplicateEntity |
Decides the duplicate policy per open, instead of duplicateEntityPolicy. A new function starts the layout again. |
|
SlotFallback |
ComponentType<{ slot: string }> |
Built-in layout only. Rendered in a slot that holds nothing, or whose renderer returns nothing. Without it, such a slot is blank, and while every slot is blank the layout shows ChatViewEmptyPlaceholder. |
|
slotFallbackComponents |
Partial<Record<string, ComponentType<{ slot: string }>>> |
Built-in layout only. SlotFallback per slot name. Wins over SlotFallback. |
|
slotRenderers |
ChatViewSlotRenderers |
renderers for channelList, threadList and userProfile |
How Slot and the built-in layout render each kind of thing a slot holds. See slotRenderers. Keep it stable. |
viewActionSlotResolvers |
Partial<Record<ChatView, ViewActionSlotResolvers>> |
Picks the slot for an opened channel or thread, per view. See viewActionSlotResolvers. Keep it stable. |
|
views |
{ channels?: ReactNode; threads?: ReactNode } |
The content of each view. The shown view's content renders in a div with the class str-chat__chat-view__channels or str-chat__chat-view__threads. The views are channels and threads only: the ChatView type is "channels" | "threads". |
"Keep it stable" means declaring the value outside the component, or memoizing it. Views and slots explains why layouts has to be stable too.
useChatViewContext
useChatViewContext() reads the ChatView around the component. It throws outside a ChatView.
| Member | Type | Description |
|---|---|---|
activeView |
"channels" | "threads" |
The view that's shown. |
setActiveView |
(view: ChatView) => void |
Shows a view. Each view keeps what its slots hold. |
layoutController |
LayoutController |
The layout's state and the methods that change it. See The layout controller. |
registerViewActionSlotResolvers |
(view: ChatView, resolvers?: ViewActionSlotResolvers) => void |
Sets the slot resolvers of a view, as the viewActionSlotResolvers prop does. undefined removes them. |
resolveActionTargetSlot |
(view: ChatView, args: ResolveViewActionTargetSlotArgs) => string | undefined |
Runs the view's resolver for args.action. |
entityInferers |
ChatViewEntityInferer[] |
The entityInferrers prop. Nothing reads it. |
ChatViewContext itself is exported too. useContext(ChatViewContext) returns undefined outside a ChatView instead of throwing, which suits a component that also renders without one.
Switching between the channel list and the thread list has an example of setActiveView. For opening and closing channels and threads, use useWorkspaceNavigation() instead: it works with any layout.
DisposedChannelRelease
DisposedChannelRelease empties every slot of the shown view whose channel stream-chat disposed of: deleted, or the current user removed from it. It takes no props and renders nothing. It's opt-in: render it once inside ChatView, as one of its children. The built-in layout doesn't render ChatView's children, so it can't host it. Disposed channels explains when a channel is disposed of.
WorkspaceLayout
A frame of slots: an optional rail, then a row of slots. The built-in workspace layout is a WorkspaceLayout, and a view's content can use one too.
| Prop | Type | Default | Description |
|---|---|---|---|
slots |
WorkspaceLayoutSlot[] |
required | The slots, in order. Each is { slot: string; content?: ReactNode }, rendered as a Slot with content as its children. |
navRail |
ReactNode |
Rendered in the rail, str-chat__chat-view__workspace-layout-nav-rail, before the slots. Without it, there's no rail. |
While no entry has content that renders something (every one is undefined, null or a boolean), WorkspaceLayout shows ChatViewEmptyPlaceholder in place of the slots. So give each slot its content. An entry without content, next to one with content, renders what its slot holds through slotRenderers.
import {
ChannelHeader,
ChannelNavigation,
MessageComposer,
MessageList,
ThreadHeader,
ThreadList,
} from "stream-chat-react";
import {
ChannelSlot,
ChatView,
ChatViewEmptyPlaceholder,
ThreadSlot,
WorkspaceLayout,
} from "stream-chat-react/slot-layout";
import type { LayoutDescriptor } from "stream-chat-react/slot-layout";
// "list" isn't one of the views' slots, so nothing opens there.
const layouts: LayoutDescriptor[] = [
{ id: "channels", slots: ["channel", "thread"] },
{ id: "threads", slots: ["thread"] },
];
const threadPanel = {
content: (
<ThreadSlot slot="thread">
<ThreadHeader />
<MessageList />
<MessageComposer />
</ThreadSlot>
),
slot: "thread",
};
const ChannelsView = () => (
<WorkspaceLayout
navRail={<ChatView.Selector />}
slots={[
{ content: <ChannelNavigation />, slot: "list" },
{
content: (
<ChannelSlot fallback={<ChatViewEmptyPlaceholder />} slot="channel">
<ChannelHeader />
<MessageList />
<MessageComposer />
</ChannelSlot>
),
slot: "channel",
},
threadPanel,
]}
/>
);
const ThreadsView = () => (
<WorkspaceLayout
navRail={<ChatView.Selector />}
slots={[{ content: <ThreadList />, slot: "list" }, threadPanel]}
/>
);
export const Workspace = () => (
<ChatView
layouts={layouts}
views={{ channels: <ChannelsView />, threads: <ThreadsView /> }}
/>
);Each view gets its content: the selector can show either view, and a view without content renders nothing, the selector included. The channels view has a thread slot so that a reply thread opens beside the channel. With only a channel slot, the thread would replace the channel there, and ChannelSlot would show its fallback.
ChatViewEmptyPlaceholder
The SDK's empty state for a layout: an icon and "No chat selected", or "No thread selected" in the threads view. It takes no props. WorkspaceLayout shows it while no slot has content, and it works as a fallback or SlotFallback. Outside a ChatView, it uses the channels wording.
Slot
Renders what one slot holds, and the layers on top of it, through slotRenderers.
| Prop | Type | Default | Description |
|---|---|---|---|
slot |
string |
required | The slot to render. |
children |
ReactNode |
Rendered instead of what the slot holds. Layers still render on top of it. | |
className |
string |
Added to the slot element. |
It renders a section with the class str-chat__chat-view__slot and data-slot set to the slot name. A hidden slot also gets str-chat__chat-view__slot--hidden and aria-hidden, and stays mounted. While the slot has layers, what it holds and each layer stay mounted: only the top one shows, and the ones beneath get the hidden attribute. Pushing the first layer and removing the last one mount what the slot holds again, so it loses its scroll position.
Slot declares its slot as the panel its content renders in, so useWorkspacePanel() inside returns the slot name. Panels explains what the panel is for.
Entity adapters
Three components render a slot's channel, thread or thread list with the matching SDK component, around their children. Rendering a slot compares them with Slot.
Always pass slot. Without it, ChannelSlot and ThreadSlot render the first channel or thread in the view, which is only right in a layout with one of each. An adapter only displays: it never moves a channel or thread from another slot.
ChannelSlot
Renders a Channel for the channel in its slot. It shows what the slot holds underneath any layer, never the layer.
| Prop | Type | Default | Description |
|---|---|---|---|
slot |
string |
The slot to read. | |
fallback |
ReactNode |
null |
Rendered while the slot holds no channel. |
children |
ReactNode |
Rendered inside the Channel. |
It also takes every prop of Channel except channel, and passes them on.
ThreadSlot
Renders a Thread for the thread in its slot. Like ChannelSlot, it shows what the slot holds underneath any layer.
| Prop | Type | Default | Description |
|---|---|---|---|
slot |
string |
The slot to read. | |
fallback |
ReactNode |
null |
Rendered while the slot holds no thread. |
hideIfEmpty |
boolean |
true |
Hides the slot while it holds no thread, and shows it again when one opens. Needs slot. |
children |
ReactNode |
Rendered inside the Thread. |
It also takes every prop of Thread except thread. ThreadSlot doesn't mark the thread as on screen: call useActiveThread inside it.
ThreadListSlot
Puts a thread list in a slot of the shown view, and renders that slot as a Slot. The slot it uses is the first of: slot, when the view has it; the slot that already holds a thread list; the first empty slot; the view's first slot. When slot is passed and another slot holds the list, it empties that one.
| Prop | Type | Default | Description |
|---|---|---|---|
slot |
string |
The slot to put the thread list in. | |
fallback |
ReactNode |
null |
Rendered when the view has no slot. While children aren't passed, it's also rendered in place of the thread list. |
children |
ReactNode |
Rendered in the slot instead of the thread list. |
Without children or fallback, the slot renders slotRenderers.threadList, which is ThreadList by default.
View selector
ChatView.Selector renders the buttons that switch views, in a role="navigation" element labelled "Chat view controls".
| Prop | Type | Default | Description |
|---|---|---|---|
iconOnly |
boolean |
true |
Shows icons without their labels. Passed to each button. |
itemSet |
ChatViewSelectorEntry[] |
defaultChatViewSelectorItemSet |
The buttons, in order. |
Each entry is { Component, type }: Component receives iconOnly, and type is the entry's unique key. The default set is the channels button and the threads button. A button of your own doesn't have to switch views: there are only the two, channels and threads. This set adds a button that opens a support channel:
import { useMemo } from "react";
import { useChatContext, useWorkspaceNavigation } from "stream-chat-react";
import {
ChatView,
ChatViewSelectorButton,
defaultChatViewSelectorItemSet,
} from "stream-chat-react/slot-layout";
import type {
ChatViewSelectorEntry,
ChatViewSelectorItemProps,
} from "stream-chat-react/slot-layout";
const SupportIcon = () => (
<svg aria-hidden="true" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="currentColor" r="8" />
</svg>
);
const SupportSelectorButton = ({ iconOnly }: ChatViewSelectorItemProps) => {
const { client } = useChatContext();
const { isChannelActive, openChannel } = useWorkspaceNavigation();
const channel = useMemo(
() => client.channel("messaging", "support"),
[client],
);
return (
<ChatViewSelectorButton
Icon={SupportIcon}
iconOnly={iconOnly}
isActive={isChannelActive(channel.cid)}
onClick={(event) => {
// openChannel places the channel; watching loads it.
channel.watch().catch((error) => console.error(error));
openChannel(channel, { event });
}}
text="Support"
/>
);
};
const itemSet: ChatViewSelectorEntry[] = [
...defaultChatViewSelectorItemSet,
{ Component: SupportSelectorButton, type: "support" },
];
export const Selector = () => <ChatView.Selector itemSet={itemSet} />;The individual buttons
Render them directly when a rail of your own needs its own arrangement.
| Component | Props | Description |
|---|---|---|
ChatViewChannelsSelectorButton |
iconOnly (true) |
Shows the channels view. Its icon is filled while that view is shown. |
ChatViewThreadsSelectorButton |
iconOnly (true) |
Shows the threads view, with a badge counting the threads that have unread replies. Its aria-label includes the count. |
ChatViewSelectorButton |
see below | The button both are built on. |
ChatViewSelectorButton takes every prop of a button, plus:
| Prop | Type | Default | Description |
|---|---|---|---|
Icon |
ComponentType |
The icon. | |
ActiveIcon |
ComponentType |
The icon while isActive. Falls back to Icon. |
|
isActive |
boolean |
Marks the button as the current one, with aria-current. |
|
text |
string |
The label. With iconOnly, it's shown as a tooltip and used as the aria-label. |
|
iconOnly |
boolean |
true |
Hides the label next to the icon. |
children |
ReactNode |
Rendered instead of the icon. |
useActiveThread
useActiveThread({ activeThread }) marks a thread as on screen while the window is visible and focused, and unmarks it when the window is hidden or loses focus, when the component unmounts, and when activeThread changes. It returns nothing.
| Option | Type | Description |
|---|---|---|
activeThread |
Thread | undefined |
The thread on screen. undefined does nothing. |
While a thread is on screen, its new replies are marked read, and a thread that went out of date, after a lost connection for example, reloads. ThreadSlot doesn't call it, so call it from a component inside the slot:
const ActiveThread = () => {
const thread = useThreadContext();
useActiveThread({ activeThread: thread });
return null;
};It doesn't need ChatView: Providing your own navigation calls it without one.
See also
- Workspace with ChatView: views, slots, where things land, and rendering a slot.
- Slot layout: the bindings, the layout controller and the hooks that read the layout.
- Workspace navigation API: the navigation
ChatViewprovides.