Thread list UI

The thread list is the screen of threads the user takes part in. 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 ThreadList component

<ThreadList isFocused={isFocused} onThreadSelect={onThreadSelect} /> renders the threads of client.threads, the thread manager. What it does, and when:

When ThreadList
isFocused is true Activates client.threads: the first page loads when the list has none, and the list reloads when threads got replies while it was inactive. It's true by default.
isFocused turns false, or it unmounts Deactivates client.threads
client.threads.paginator.state changes Renders a thread list item for each thread, in a React Native FlatList, in the server's order: unread threads first, then by latest reply
The user scrolls to the end Loads the next page
The user taps an item Calls onThreadSelect(thread), and your app shows the thread. Without onThreadSelect, the tap does nothing.

Pass the focus state of the list's screen as isFocused, such as useIsFocused() from React Navigation, so the list stays active only while the user can see it. Below the items, the list renders a NotificationList for the notifications published to the thread list; see Notifications UI.

Thread list items

Each thread list item reads its thread's stores and provides what it reads to the components inside it:

The item shows Read from
The avatar of the latest reply's author thread.messagePaginator.aggregateState: latestMessage
The channel's name thread.state: channel
The parent message, or "Draft:" and the draft when one was left thread.state: parentMessage, and the thread's composer: its text and attachments
The sender before the parent message: "You:" with a delivery mark for the user's own, the sender's name for another member's, except in a direct message channel.messageReceiptsTracker.snapshotStore, and the channel's read events setting
Up to 3 participants' avatars parentMessage.thread_participants
The reply count parentMessage.reply_count
The time of the latest reply, or the time the parent was deleted thread.messagePaginator.aggregateState: latestMessage, and thread.state: deletedAt
The unread count, when there is one thread.state: read[userId].unreadMessageCount. Not shown for a deleted parent message.

ThreadListItem provides these to the components inside it, which read them with useThreadListItemContext(): thread, channel, parentMessage, lastReply, ownUnreadMessageCount, draftMessage, dateString and deletedAtDateString. The default item renders the ThreadListItem key with no props.

The unread 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 new threads". Tapping it reloads the list, which brings those threads in at their new position. While it reloads, the banner reads "Loading...". If the reload fails, the banner reads "Couldn't load new threads. Tap to retry", and tapping it tries again.

Loading, empty and error states

ThreadList shows a state of its own while the first page loads, while the next page loads, and when it has no threads. It has no error view of its own for the first page: a query that fails leaves the list as it was, or the empty placeholder when nothing was loaded, and the error is recorded as lastQueryError in client.threads.paginator.state. List states UI lists each state's default and override key.

Replacing parts

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

Override key Replaces
ThreadListComponent The whole thread list: the banner and the items. Reads useThreadsContext().
ThreadListItem An item. Gets no props; reads useThreadListItemContext().
ThreadListItemMessagePreview The parent message or draft in an item
ThreadMessagePreviewDeliveryStatus The sender and delivery mark before the parent message in an item
ThreadListUnreadBanner The unread banner
ThreadListLoadingIndicator, ThreadListLoadingMoreIndicator, ThreadListEmptyPlaceholder The loading and empty states

The default item, ThreadListItemComponent, isn't exported, so a replacement for it can't render the default inside it.

This thread list renders compact items of its own, with the channel's name, the parent message's text and the unread count:

src/ThreadsScreen.tsx
import { Pressable, StyleSheet, Text, View } from "react-native";
import { useIsFocused } from "@react-navigation/native";
import type { Thread as StreamThread } from "stream-chat";
import {
  ThreadList,
  useChannelPreviewDisplayName,
  useThreadListItemContext,
  useThreadsContext,
  WithComponents,
} from "stream-chat-react-native";

const CompactThreadRow = () => {
  const { channel, ownUnreadMessageCount, parentMessage, thread } =
    useThreadListItemContext();
  const { onThreadSelect } = useThreadsContext();
  const channelName = useChannelPreviewDisplayName(channel);

  return (
    <Pressable onPress={() => onThreadSelect?.(thread)} style={styles.row}>
      <View style={styles.text}>
        <Text numberOfLines={1} style={styles.channel}>
          {channelName}
        </Text>
        <Text numberOfLines={1}>{parentMessage?.text}</Text>
      </View>
      {ownUnreadMessageCount > 0 ? <Text>{ownUnreadMessageCount}</Text> : null}
    </Pressable>
  );
};

const overrides = { ThreadListItem: CompactThreadRow };

export const ThreadsScreen = ({
  onThreadSelect,
}: {
  onThreadSelect: (thread: StreamThread) => void;
}) => {
  const isFocused = useIsFocused();

  return (
    <WithComponents overrides={overrides}>
      <ThreadList isFocused={isFocused} onThreadSelect={onThreadSelect} />
    </WithComponents>
  );
};

const styles = StyleSheet.create({
  channel: { fontWeight: "600" },
  row: { alignItems: "center", flexDirection: "row", gap: 8, padding: 12 },
  text: { flex: 1 },
});

The item reads the parent message and the unread count from the item's context, which follows thread.state, so it updates when a reply arrives. The Custom thread list UI cookbook builds a thread list of its own.

Used in