# Thread UI

A thread is a message and its replies, shown apart from the channel. `Thread` shows one thread, and the user's threads are listed on [Thread list UI](https://getstream.io/chat/docs/sdk/react-native/ui/components/thread-list-ui/). This page covers `Thread`, the parent message, a thread header, replies sent to the channel too, and the parts you can replace. The props are in the [Thread reference](https://getstream.io/chat/docs/sdk/react-native/component-reference/thread-list/thread/).

## The Thread component

`<Thread thread={thread}>` shows the thread passed as `thread`. Like `Channel`, it renders the parts you give it as children:

```tsx title="src/ThreadScreen.tsx"
import type { Thread as StreamThread } from "stream-chat";
import { MessageComposer, MessageList, Thread } from "stream-chat-react-native";

export const ThreadScreen = ({ thread }: { thread: StreamThread }) => (
  <Thread thread={thread}>
    <MessageList />
    <MessageComposer />
  </Thread>
);
```

Here is what `Thread` does:

- It gives the thread to its children. `useThreadContext()` returns it, and `useChannel()` returns the thread's channel, so a `Thread` needs no `Channel` around it. Render it on a screen of its own, next to the channel's screen.
- The children then work on the thread's objects. `MessageList` renders the replies in `thread.messagePaginator`, and `MessageComposer` writes into `thread.messageComposer`.
- Around the children it renders what `Channel` renders: the keyboard handling, the contexts the message list and composer read, and the attachment picker. It takes the same configuration props as `Channel`, which it doesn't inherit from one; see [Configuration shared with Thread](https://getstream.io/chat/docs/sdk/react-native/ui/components/channel-ui/#configuration-shared-with-thread).
- It loads the first page of replies, unless the thread already holds some. A thread from the thread list arrives with its latest replies.
- It shows the newest replies when the replies were left on an older page, for example after a jump to a reply, unless a jump to a reply already landed or is still loading. When the newest page is still loaded, this needs no request. It's the same behavior as `Channel` has for its messages; see [The Channel component](https://getstream.io/chat/docs/sdk/react-native/ui/components/channel-ui/#the-channel-component).
- It reloads the thread while its state is stale: when the thread was created from a message that has replies or in a channel that isn't watched, or after the thread stopped receiving its channel's events.
- It watches the thread's channel once when nobody has loaded it, which happens for a thread opened from the thread list. That gives the user's capabilities in the channel to the message menu, the reactions and the composer.
- It activates the thread while it's mounted. An active thread receives its events even when the thread list doesn't hold it, and marks new replies read as they arrive.
- When it unmounts, it calls `onThreadDismount`, for example to clear your app's record of the open thread.

When the first page of replies fails to load, `Thread` renders `LoadingErrorIndicator` in place of its children, "Error loading replies...". Tapping it loads the replies again.

Get the thread from the client, never with `new Thread()`. `onThreadSelect` on `MessageList` and `ThreadList` gives you the thread the user picked, `client.threads.ensure({ channel, parentMessage })` returns the thread for a message and creates it when needed, and `client.threads.get(id)` returns one the client already has. Either way the client keeps one instance per thread and sends it its events. [What channels and threads hold](https://getstream.io/chat/docs/sdk/react-native/state/channels-and-threads/what-channels-and-threads-hold/) explains the thread's state.

The SDK doesn't navigate: the replies button on a message calls `onThreadSelect` on the message list, and your app shows the thread's screen, as in [Quick start](https://getstream.io/chat/docs/sdk/react-native/getting-started/quick-start/#add-threads). Without `onThreadSelect`, the button does nothing.

### What's different in a thread

The message list and the composer behave as in a channel, with these differences:

- The unread notification and the unread count on the scroll-to-bottom button don't show. The thread marks itself read while it's open.
- A thread with no replies shows its parent message rather than the empty state.
- The attachment picker has no poll button: polls can't be created in a thread.
- The composer shows the also-send-to-channel checkbox.
- The typing indicator shows the users typing in this thread only.
- The message menu offers **Quote Reply**, and the quote goes into the thread's composer. **Thread Reply** and **Mark as unread** aren't offered.
- The message list follows new replies, and scrolls to the user's own reply when they send it.

## The parent message

The parent message isn't a child you place. The message list renders it above the replies, because it has to scroll with them: the message, a "4 Replies" divider under it, and a spinner while older replies load. It's the list's older-end component, `ThreadFooterComponent`, which reads the parent message and the reply count from `thread.state`, so it updates when they change. Tapping or long-pressing the parent message does nothing. When the current user sent it, it shows the sending, sent, delivered or read mark like any of the user's messages, as `MessageStatus` explains on [Message UI](https://getstream.io/chat/docs/sdk/react-native/ui/components/message-ui/#the-footer-and-the-status).

To change it, pass your own component as the list's prop for the older end, `FooterComponent` on `MessageList` or `HeaderComponent` on `MessageFlashList`. `ThreadFooterComponent` is exported, so your component can render it with your additions around it. It takes `parentMessagePreventPress={false}` to make the parent message pressable.

## A thread header

The SDK has no thread header component. Set the native header's title through the screen's options, or render a header of your own as the first child of `Thread`, as for a channel (see [The channel header](https://getstream.io/chat/docs/sdk/react-native/ui/components/channel-ui/#the-channel-header)). Inside `Thread`, `useTypingString()` returns who is typing in the thread, and `thread.state` holds the parent message:

```tsx title="src/ThreadHeader.tsx"
import { StyleSheet, Text, View } from "react-native";
import type { ThreadState } from "stream-chat";
import {
  useStateStore,
  useThreadContext,
  useTypingString,
} from "stream-chat-react-native";

const parentMessageSelector = ({ parentMessage }: ThreadState) => ({
  parentMessage,
});

export const ThreadHeader = () => {
  const { thread } = useThreadContext();
  const { parentMessage } =
    useStateStore(thread?.state, parentMessageSelector) ?? {};
  const typing = useTypingString();
  const author = parentMessage?.user?.name ?? parentMessage?.user?.id;

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Thread</Text>
      <Text numberOfLines={1}>
        {typing || (author ? `with ${author}` : "")}
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: { alignItems: "center", paddingVertical: 8 },
  title: { fontSize: 16, fontWeight: "600" },
});
```

## Replies sent to the channel

In a thread, the composer shows a checkbox, "Also send to channel". With it checked, the reply is also posted to the channel's message list. It toggles `showReplyInChannel` in the thread composer's state. `allowThreadMessagesInChannel={false}` on `Thread` hides it.

A reply sent to both places shows a mark above it, `SentToChannelHeader`: "Replied to a thread" in the channel, and "Also sent in channel" in the thread. The mark gets a "View" link when you pass `onAlsoSentToChannelHeaderPress` to `Channel` or `Thread`. In the channel, the handler gets the reply's id and its thread, so your app can open the thread at that reply. In the thread, it gets the reply's id, so your app can open the channel at that message. A jump to the reply is a paginator call: see [Jumps on screen](https://getstream.io/chat/docs/sdk/react-native/ui/components/message-list-ui/#jumps-on-screen).

## Replacing parts

Register replacements with `WithComponents`. [Overriding components](https://getstream.io/chat/docs/sdk/react-native/ui/components/overriding-components/) explains how overrides apply.

| Override key            | Replaces                                                     |
| ----------------------- | ------------------------------------------------------------ |
| `SentToChannelHeader`   | The mark on a reply sent to the channel too                  |
| `LoadingErrorIndicator` | The error state when the first page of replies fails to load |

`Thread` has no other override key: render other children in it. The parent message is the list's prop, as [The parent message](#the-parent-message) explains. The also-send-to-channel checkbox is part of `InputView`; replace that key to change it. Inside a thread, the message list and the composer take the same override keys as anywhere else; [Message list UI](https://getstream.io/chat/docs/sdk/react-native/ui/components/message-list-ui/#replacing-parts) and [Message composer UI](https://getstream.io/chat/docs/sdk/react-native/ui/components/message-composer-ui/#replacing-parts) list them.

## Used in

- [Replies and threads](https://getstream.io/chat/docs/sdk/react-native/features/messaging/replies-and-threads/)
- [Thread header](https://getstream.io/chat/docs/sdk/react-native/ui-cookbooks/headers/thread-header/)

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/ui/components/thread-ui/](https://getstream.io/chat/docs/sdk/react-native/ui/components/thread-ui/).