Thread list UI

The thread list is the column of threads the user takes part in, shown in the threads view. Each thread list item shows one thread's channel, parent message and latest reply. Use this page to know what <ThreadList/> loads and renders, what each thread list item shows and reads, and which parts you can replace. An open thread is on Thread UI. The props are in the ThreadList and ThreadListItem references.

The thread list, with its parts numbered: the header, the unseen threads banner, the parts of a thread list item, and the open thread

This is the default thread list, with the Hiking club thread open. Its parts, numbered on the first item:

Part Explained in
1. The header Header
2. The unseen threads banner The unseen threads banner
3. The latest reply author's avatar Thread list items
4. The channel's title Thread list items
5. The parent message, summarized Thread list items
6. The participants' avatars Thread list items
7. The reply count Thread list items
8. The latest reply's time Thread list items
9. The unread count Thread list items
10. The open thread, selected Thread list items

The ThreadList component

<ThreadList/> renders the threads of client.threads, the thread manager. What it does, and when:

When <ThreadList/>
It mounts Reloads the list
The page is visible Activates client.threads, so the list follows new replies
The page is hidden, or it unmounts Deactivates client.threads
client.threads.paginator.state changes Renders a <ThreadListItem/> for each thread, in the server's order: unread threads first, then by latest reply
The user scrolls to the end Loads the next page
The user clicks an item Opens the thread with openThread(thread) from useWorkspaceNavigation(). Without a layout, this does nothing; see Navigation.
The user ⌘/ctrl-clicks an item Asks the layout to open the thread beside the current one, where the layout has room

The list is virtualized with react-virtuoso; its virtuosoProps prop passes props to it. Below the items, it renders the notifications published to the thread list.

<ThreadListHeader/> shows the "Threads" title. <ThreadList/> renders it above the items, without props. It takes the same StartContent and EndContent props as <ChannelListHeader/>. Register a component under the ThreadListHeader key that renders <ThreadListHeader/> with StartContent or EndContent, as Channel list UI → Header shows. To show content only while a thread is open, return null from it while openThreads from useWorkspaceNavigation() is empty.

Thread list items

Each thread list item is a <ThreadListItem/>. It provides its thread to the components inside and renders ThreadListItemUI, the item's look, which reads the thread's stores:

The item shows Read from
The latest reply author's avatar thread.messagePaginator.aggregateState: latestMessage
The channel's title thread.state: channel
The parent message, summarized thread.state: parentMessage
Up to 3 participants' avatars thread.state: participants
The reply count thread.state: replyCount
The latest reply's time thread.messagePaginator.aggregateState: latestMessage
The unread count, when there is one thread.state: read[userId].unreadMessageCount
Selected isThreadActive(thread.id) from useWorkspaceNavigation(): the thread is open
Highlighted for 2 seconds the thread has just come into the list from the unseen threads banner

Anything rendered inside an item retrieves its thread with useThreadListItemContext(). ThreadListItemUI renders a button: it takes the button's props, and accessibleLabelConfig, which configures the item's accessible name; see the ThreadListItem reference.

The unseen threads banner

A thread that isn't in the loaded list can get a reply, for example an older thread someone answers. The thread manager records it in client.threads.state as unseen, and the list shows a banner above the items: "2 unread threads". Clicking it reloads the list, which brings those threads in at their new position. While it reloads, the banner reads "Loading...".

Loading, empty and error states

<ThreadList/> shows a state of its own while the first page loads, when it fails, while the next page loads, and when it has no threads. List states UI lists each state's default and <WithComponents/> key, and explains what happens when a query fails.

Replacing parts

Register replacements with <WithComponents/>. Overriding components explains how overrides apply.

Component to override (<WithComponents/> key) Replaces
ThreadListItem The whole item. Gets thread.
ThreadListItemUI The item's look
ThreadListUnseenThreadsBanner The unseen threads banner
FirstPageLoadingIndicator, ListLoadErrorIndicator, ThreadListLoadingIndicator, ThreadListEmptyPlaceholder The loading, error and empty states
ThreadListHeader The list header. Register a component that renders <ThreadListHeader/> to keep the default and add content

The defaults of ThreadListUnseenThreadsBanner, ThreadListEmptyPlaceholder and ThreadListLoadingIndicator aren't exported, so a replacement for one of them can't render the default inside it.

Here is a ThreadsSidebar component that keeps the default item and shows the thread's title above it, when the thread has one:

src/ThreadsSidebar.tsx
import type { ThreadState } from "stream-chat";
import type { ThreadListItemUIProps } from "stream-chat-react";
import {
  ThreadList,
  ThreadListItemUI,
  useStateStore,
  useThreadListItemContext,
  WithComponents,
} from "stream-chat-react";

const titleSelector = ({ title }: ThreadState) => ({ title });

const ItemWithTitle = (props: ThreadListItemUIProps) => {
  const thread = useThreadListItemContext();
  const { title } = useStateStore(thread?.state, titleSelector) ?? {};

  return (
    <div className="thread-list-item-with-title">
      {title && (
        <span className="thread-list-item-with-title__title">{title}</span>
      )}
      <ThreadListItemUI {...props} />
    </div>
  );
};

const overrides = { ThreadListItemUI: ItemWithTitle };

export const ThreadsSidebar = () => (
  <WithComponents overrides={overrides}>
    <ThreadList />
  </WithComponents>
);

Used in