# Thread

> **Info:** This is release candidate documentation for **Stream Chat React Native SDK v10**. For the latest stable version, see the [latest version (v9)](https://getstream.io/chat/docs/sdk/react-native/).

`Thread` gives one thread to the components inside it. It renders its children with the contexts a message list and a composer need, the same ones `Channel` provides, but bound to the thread's replies and composer. Render it next to `Channel`, on a screen of its own, not inside it. [Thread UI](https://getstream.io/chat/docs/sdk/react-native/v10/ui/components/thread-ui/#the-thread-component) explains what it does, and [Replies and threads](https://getstream.io/chat/docs/sdk/react-native/v10/features/messaging/replies-and-threads/) covers the feature.

## Best practices

- Get the thread from the client, never from `new Thread()`. `onThreadSelect` on `MessageList`, `MessageFlashList` and `ThreadList` gives you the thread the user picked, and `client.threads.ensure({ channel, parentMessage })` returns the thread for a message, creating it when needed. The client keeps one instance per thread and sends it its events.
- Compose the screen from children. `Thread` renders no message list or composer of its own: render `MessageList` or `MessageFlashList`, and `MessageComposer`, inside it.
- Pass `Thread` the same configuration you pass `Channel`, such as `keyboardVerticalOffset`, `topInset`, `audioRecordingEnabled` and `messageActions`. A thread doesn't inherit it from a channel.
- Clear your app's record of the open thread in `onThreadDismount`, for example when the user navigates back.
- A `Thread` needs no `Channel` around it. Inside it, `useChannel()` returns the thread's channel.

## Basic usage

The channel screen opens a thread when the user taps a message's replies, and the thread screen renders it:

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

export const ChannelScreen = ({
  channel,
  openThread,
}: {
  channel: StreamChannel;
  openThread: (thread: StreamThread) => void;
}) => (
  <Channel channel={channel} keyboardVerticalOffset={0}>
    <MessageList onThreadSelect={openThread} />
    <MessageComposer />
  </Channel>
);

export const ThreadScreen = ({
  onClose,
  thread,
}: {
  onClose: () => void;
  thread: StreamThread;
}) => (
  <Thread keyboardVerticalOffset={0} onThreadDismount={onClose} thread={thread}>
    <MessageList />
    <MessageComposer />
  </Thread>
);
```

`MessageList` renders the parent message above the replies, and `MessageComposer` writes into `thread.messageComposer`. [Moving between channels, threads and messages](https://getstream.io/chat/docs/sdk/react-native/v10/ui/layout/moving-between-channels-threads-and-messages/) passes the thread from one screen to the other, and opens a thread at a reply.

## What it does

While it's mounted, `Thread`:

- loads the first page of replies with `paginator.toTail()` on `thread.messagePaginator`, unless the paginator already holds some. A thread from the thread list arrives with its latest replies;
- reloads the thread with `Thread.reload()` while its state is stale;
- watches the thread's channel once when nobody has loaded it, so the user's capabilities in the channel reach the message menu, the reactions and the composer;
- activates the thread with `Thread.activate()`, so it receives its events even when the thread list doesn't hold it, and marks new replies read as they arrive. Opening a thread doesn't mark its channel read;
- routes its notifications to its own notification host.

When it unmounts, it releases the activation and calls `onThreadDismount`. When the first page of replies fails to load, it renders `LoadingErrorIndicator`, "Error loading replies...", in place of its children, and tapping it loads the replies again.

## UI customization

`Thread` has no override key. Change the screen by changing its children. Inside it, the message list and the composer take the same override keys as in a channel. To give the thread overrides of its own, wrap its children in `WithComponents`:

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

const NoTimestamp = () => null;

const threadOverrides: ComponentOverrides = { MessageTimestamp: NoTimestamp };

export const CompactThread = ({ thread }: { thread: StreamThread }) => (
  <Thread thread={thread}>
    <WithComponents overrides={threadOverrides}>
      <MessageList />
      <MessageComposer />
    </WithComponents>
  </Thread>
);
```

The parent message isn't a child you place. The message list renders it at the older end of the list as `ThreadFooterComponent`: the parent message, the reply count, and `InlineLoadingMoreThreadIndicator` while older replies load. To change it, pass your own component as `FooterComponent` on `MessageList` or `HeaderComponent` on `MessageFlashList`; [Replacing the parent message area](https://getstream.io/chat/docs/sdk/react-native/v10/features/messaging/replies-and-threads/#replacing-the-parent-message-area) shows one.

### ThreadFooterComponent props

`ThreadFooterComponent` reads the thread from `ThreadContext` and the parent message and reply count from `thread.state`, so it updates when they change. It renders nothing until the thread has its parent message.

| Prop                        | Type      | Default | Description                                                                                                   |
| --------------------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------- |
| `parentMessagePreventPress` | `boolean` | `true`  | Whether tapping and long-pressing the parent message do nothing. `false` makes it pressable like any message. |

`InlineLoadingMoreThreadIndicator` takes no props. It shows a spinner while `thread.messagePaginator` loads.

## Props

| Prop                           | Type         | Default               | Description                                                                                                                                                              |
| ------------------------------ | ------------ | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `thread`                       | `Thread`     | required              | The thread to render, a `Thread` from `stream-chat`. Get it from `onThreadSelect`, `client.threads.ensure()` or `client.threads.get()`.                                  |
| `children`                     | `ReactNode`  | none                  | The screen's content: usually `MessageList` or `MessageFlashList`, and `MessageComposer`.                                                                                |
| `allowThreadMessagesInChannel` | `boolean`    | `true`                | Shows the "Also send to channel" checkbox in the composer. `false` hides it.                                                                                             |
| `onThreadDismount`             | `() => void` | none                  | Called when `Thread` unmounts. Only the function passed on the first render is called.                                                                                   |
| `notificationHostId`           | `string`     | the thread's own host | The notification host the thread's notifications are routed to. By default, a host derived from the thread's id, which `getThreadNotificationHostId(thread.id)` returns. |

### Configuration shared with Channel

`Thread` also takes the configuration props of `Channel`, with the same types and defaults. They configure the parts inside it, and only those: a thread screen doesn't inherit the props of a channel screen. Their descriptions are on [Channel](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/channel/channel/#props). The props that only `Channel` takes, `channel`, `initializeOnMount`, `markReadOnMount` and `initialScrollToFirstUnreadMessage`, are about loading a channel; `Thread` loads its replies itself. `Channel` also takes `client` and `t`, which `Thread` doesn't.

#### Messages

These reach `MessagesContext`, which the messages read.

| Prop                             | Type                                                                            | Default                                                                  |
| -------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| `additionalPressableProps`       | `Omit<PressableProps, 'style'>`                                                 | none                                                                     |
| `customMessageSwipeAction`       | `({ channel, message, }: { channel: Channel; message: LocalMessage; }) => void` | none                                                                     |
| `disableTypingIndicator`         | `boolean`                                                                       | `false`                                                                  |
| `dismissKeyboardOnMessageTouch`  | `boolean`                                                                       | `true`                                                                   |
| `enableSwipeToReply`             | `boolean`                                                                       | `true`                                                                   |
| `FlatList`                       | `typeof NativeHandlers.FlatList \| undefined`                                   | the registered `FlatList`                                                |
| `forceAlignMessages`             | `Alignment \| boolean`                                                          | none                                                                     |
| `getMessageGroupStyle`           | `(params: MessageGroupStylesParams) => GroupStyle[]`                            | the SDK's grouping                                                       |
| `giphyVersion`                   | `keyof NonNullable<Attachment['giphy']>`                                        | `"fixed_height"`                                                         |
| `handleBan`                      | `(message: LocalMessage) => Promise<void>`                                      | none                                                                     |
| `handleBlockUser`                | `(user: LocalMessage['user']) => Promise<void>`                                 | none                                                                     |
| `handleCopy`                     | `(message: LocalMessage) => Promise<void>`                                      | none                                                                     |
| `handleDelete`                   | `(message: LocalMessage) => Promise<void>`                                      | none                                                                     |
| `handleDeleteForMe`              | `(message: LocalMessage) => Promise<void>`                                      | none                                                                     |
| `handleEdit`                     | `(message: LocalMessage) => void`                                               | none                                                                     |
| `handleFlag`                     | `(message: LocalMessage) => Promise<void>`                                      | none                                                                     |
| `handleMarkUnread`               | `(message: LocalMessage) => Promise<void>`                                      | none                                                                     |
| `handleMute`                     | `(message: LocalMessage) => Promise<void>`                                      | none                                                                     |
| `handlePinMessage`               | `((message: LocalMessage) => MessageActionType) \| null`                        | none                                                                     |
| `handleQuotedReply`              | `(message: LocalMessage) => Promise<void>`                                      | none                                                                     |
| `handleReaction`                 | `(message: LocalMessage, reactionType: string) => Promise<void>`                | none                                                                     |
| `handleRetry`                    | `(message: LocalMessage) => Promise<void>`                                      | none                                                                     |
| `handleThreadReply`              | `(message: LocalMessage) => Promise<void>`                                      | none                                                                     |
| `hasCreatePoll`                  | `boolean`                                                                       | whether polls are on for the channel                                     |
| `isAttachmentEqual`              | `(prevAttachment: Attachment, nextAttachment: Attachment) => boolean`           | none                                                                     |
| `markdownRules`                  | `MarkdownRules`                                                                 | none                                                                     |
| `messageActions`                 | `(param: MessageActionsParams) => MessageActionType[]`                          | the SDK's actions                                                        |
| `messageContentOrder`            | `MessageContentType[]`                                                          | quoted reply, gallery, files, poll, AI text, attachments, location, text |
| `messageOverlayTargetId`         | `string`                                                                        | `"@stream-io/message-root"`                                              |
| `messageSwipeToReplyHitSlop`     | `ViewProps['hitSlop']`                                                          | `{ left: width, right: width }`, with the window's width                 |
| `messageTextNumberOfLines`       | `number`                                                                        | none                                                                     |
| `myMessageTheme`                 | `DeepPartial<Theme>`                                                            | none                                                                     |
| `onAlsoSentToChannelHeaderPress` | `(payload: AlsoSentToChannelHeaderPressPayload) => void`                        | none                                                                     |
| `onLongPressMessage`             | `(payload: MessagePressableHandlerPayload) => void`                             | none                                                                     |
| `onPressInMessage`               | `(payload: MessagePressableHandlerPayload) => void`                             | none                                                                     |
| `onPressMessage`                 | `(payload: MessagePressableHandlerPayload) => void`                             | none                                                                     |
| `reactionListPosition`           | `'top' \| 'bottom'`                                                             | `"top"`                                                                  |
| `reactionListType`               | `'clustered' \| 'segmented'`                                                    | `"clustered"`                                                            |
| `selectReaction`                 | `(message: LocalMessage) => (reactionType: string) => Promise<void>`            | none                                                                     |
| `shouldShowUnreadUnderlay`       | `boolean`                                                                       | `true`                                                                   |
| `supportedReactions`             | `ReactionData[]`                                                                | the SDK's reactions                                                      |
| `urlPreviewType`                 | `'compact' \| 'full'`                                                           | `"full"`                                                                 |

#### Composer

These reach `MessageInputContext`. See [MessageInputContext](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/message-composer/messageinputcontext/).

| Prop                                 | Type                                                                       | Default                                               |
| ------------------------------------ | -------------------------------------------------------------------------- | ----------------------------------------------------- |
| `additionalTextInputProps`           | `TextInputProps`                                                           | none                                                  |
| `asyncMessagesLockDistance`          | `number`                                                                   | `50`                                                  |
| `asyncMessagesMinimumPressDuration`  | `number`                                                                   | `500`                                                 |
| `asyncMessagesSlideToCancelDistance` | `number`                                                                   | `75`                                                  |
| `attachmentPickerBottomSheetHeight`  | `number`                                                                   | `333`, or `72` with `disableAttachmentPicker`         |
| `attachmentSelectionBarHeight`       | `number`                                                                   | `72`                                                  |
| `audioRecordingEnabled`              | `boolean`                                                                  | `false`                                               |
| `audioRecordingSendOnComplete`       | `boolean`                                                                  | `false`                                               |
| `compressImageQuality`               | `number`                                                                   | none                                                  |
| `createPollOptionGap`                | `number`                                                                   | `8`                                                   |
| `focusInputOnPickerClose`            | `boolean`                                                                  | `true`                                                |
| `handleAttachButtonPress`            | `() => void`                                                               | none                                                  |
| `hasCameraPicker`                    | `boolean`                                                                  | whether an image picker is installed                  |
| `hasCommands`                        | `boolean`                                                                  | whether the channel type has commands                 |
| `hasFilePicker`                      | `boolean`                                                                  | whether a document picker is installed                |
| `hasImagePicker`                     | `boolean`                                                                  | whether an image picker or media library is installed |
| `messageInputFloating`               | `boolean`                                                                  | `false`                                               |
| `openPollCreationDialog`             | `({ sendMessage }: Pick<LocalMessageInputContext, 'sendMessage'>) => void` | none                                                  |
| `setInputRef`                        | `(ref: TextInputRef \| null) => void`                                      | none                                                  |

`hasCreatePoll` has no effect in a thread: the attachment picker offers no poll button there.

#### Attachment picker

These reach `AttachmentPickerContext`. See [Attachment picker](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/message-composer/attachment-picker/).

| Prop                                    | Type      | Default                        |
| --------------------------------------- | --------- | ------------------------------ |
| `bottomInset`                           | `number`  | `0`                            |
| `disableAttachmentPicker`               | `boolean` | `true` without a media library |
| `numberOfAttachmentImagesToLoadPerCall` | `number`  | `25`                           |
| `numberOfAttachmentPickerImageColumns`  | `number`  | `3`                            |
| `shouldRenderAttachmentPicker`          | `boolean` | `true`                         |
| `topInset`                              | `number`  | `0`                            |

#### Message list

These reach `ChannelContext`, which the message list reads.

| Prop                                  | Type      | Default  |
| ------------------------------------- | --------- | -------- |
| `allowDateSeparatorForSystemMessages` | `boolean` | `false`  |
| `enableMessageGroupingByUser`         | `boolean` | `true`   |
| `enforceUniqueReaction`               | `boolean` | `false`  |
| `hideDateSeparators`                  | `boolean` | `false`  |
| `hideStickyDateHeader`                | `boolean` | `false`  |
| `maxTimeBetweenGroupedMessages`       | `number`  | no limit |

#### Keyboard, audio and capabilities

| Prop                                  | Type                                      | Default                          |
| ------------------------------------- | ----------------------------------------- | -------------------------------- |
| `additionalKeyboardAvoidingViewProps` | `Partial<KeyboardCompatibleViewProps>`    | none                             |
| `allowConcurrentAudioPlayback`        | `boolean`                                 | `false`                          |
| `disableKeyboardCompatibleView`       | `boolean`                                 | `false`                          |
| `keyboardBehavior`                    | `KeyboardCompatibleViewProps['behavior']` | `"padding"`                      |
| `keyboardVerticalOffset`              | `number`                                  | `86.5` on iOS, `-300` on Android |
| `overrideOwnCapabilities`             | `Partial<OwnCapabilitiesContextValue>`    | none                             |

## Related hooks

| Hook                    | Returns                                                                                |
| ----------------------- | -------------------------------------------------------------------------------------- |
| `useThreadContext()`    | `{ thread, allowThreadMessagesInChannel }`; `thread` is `undefined` outside a `Thread` |
| `useChannel()`          | inside a thread, the thread's channel                                                  |
| `useMessagePaginator()` | inside a thread, `thread.messagePaginator`                                             |
| `useMessageComposer()`  | inside a thread, `thread.messageComposer`                                              |
| `useTypingString()`     | who is typing in the thread                                                            |

See [ThreadContext](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/thread-list/threadcontext/), [useThreadContext](https://getstream.io/chat/docs/sdk/react-native/v10/reference/hooks/state-and-entity-hooks/#usethreadcontext) and, for the thread's state and methods, [Thread and thread manager](https://getstream.io/chat/docs/sdk/react-native/v10/reference/state/thread-and-thread-manager/).

---

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