# Message hooks

> **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/).

These hooks give a component the message it renders and the handlers of that message: its actions, its operations, its receipts and its reactions. They're exported from `stream-chat-react-native` and `stream-chat-expo`.

`Message` builds the handlers once for the message it renders and puts them in `MessageContext`. Inside a custom message component, read them from `useMessageContext()` or `useMessageActionHandlers()` instead of building them again. The context's values are on [MessageContext](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/message/messagecontext/), and the default actions that call the handlers on [Actions](https://getstream.io/chat/docs/sdk/react-native/v10/ui/customizing/actions/#message-actions). Two more hooks for a message have pages of their own: `useMessageReminder` on [Reminder hooks](https://getstream.io/chat/docs/sdk/react-native/v10/reference/hooks/reminder-hooks/), and `useStreamingMessage` on [AI hooks](https://getstream.io/chat/docs/sdk/react-native/v10/reference/hooks/ai-hooks/).

Each entry gives the signature, then:

- **Returns:** what the hook gives back.
- **Reads:** the store or the context it reads.
- **Re-renders:** what makes the component render again.
- **Needs:** where the component must sit, and what the hook does outside that place.

## useMessageContext

Returns the message being rendered, and what `Message` worked out for it.

```ts
useMessageContext(): MessageContextValue
```

- **Returns**, among others:
  - `message`: the `LocalMessage`, and `channel`, its channel.
  - `isMyMessage`, `alignment`, `groupStyles`, `lastGroupMessage` and `showAvatar`: where and how the message sits in the list.
  - `images`, `videos`, `files` and `otherAttachments`: its attachments by kind. `reactions` and `hasReactions`: its reactions.
  - `messageActionHandlers`: the handlers behind the message's actions, the same object `useMessageActionHandlers()` returns.
  - `handleReaction(type)`: what tapping a reaction does. It calls the `handleReaction` prop of `Channel` or `Thread`, then toggles the reaction. A `selectReaction` prop on `Channel` or `Thread` replaces it. `undefined` on a failed message or an error message.
  - `onOpenThread()`, `onPress`, `onLongPress`, `showMessageOverlay()` and `dismissOverlay()`: what pressing the message does, and the message menu.
- **Reads:** the context `Message` provides. No store.
- **Re-renders:** when `Message` provides a new value, which happens when the message or one of the values worked out from it changes.
- **Needs:** a `Message`. Outside one it returns an empty object, typed as the full value, so check `message` before you use it there.

## useMessagesContext

Returns the message settings and handlers set on `Channel` or `Thread`.

```ts
useMessagesContext(): MessagesContextValue
```

- **Returns:** the props of `Channel` and `Thread` that configure messages: the `handle*` callbacks, `messageActions`, `supportedReactions`, `myMessageTheme`, `messageContentOrder` and the rest.
- **Reads:** the context `Channel` and `Thread` provide. No store.
- **Re-renders:** when the channel changes, or one of a few of those props does, such as `messageContentOrder`, `myMessageTheme` or the number of `supportedReactions`. A new `handle*` callback or `messageActions` function after mount doesn't provide a new value.
- **Needs:** a `Channel` or a `Thread`. Throws outside both.

The values are listed on [MessagesContext](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/message-list/messagescontext/).

## useMessageActionHandlers

Returns the handlers behind the actions of the message being rendered.

```ts
useMessageActionHandlers(): MessageActionHandlerCallbacks
```

- **Returns:** one handler for each action. They act on the message in context and take no message argument:

  | Handler                       | What it does                                                                                                                                                        |
  | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | `handleCopyMessage()`         | Copies the text, translated into the user's language when a translation exists, and publishes a `clipboard:message:copy:*` notification. Does nothing without text. |
  | `handleDeleteMessage()`       | Asks for confirmation, then deletes the message and publishes an `api:message:delete:*` notification.                                                               |
  | `handleDeleteForMeMessage()`  | Deletes the message for the current user only. No confirmation and no notification. Rejects when the request fails.                                                 |
  | `handleEditMessage()`         | Starts editing the message in the composer, once the keyboard is open.                                                                                              |
  | `handleQuotedReplyMessage()`  | Sets the message as the composer's quoted message.                                                                                                                  |
  | `handleResendMessage()`       | Sends a failed message again. Rejects when the retry fails.                                                                                                         |
  | `handleFlagMessage()`         | Asks for confirmation, then flags the message with `client.moderation.flagMessage({ message_id })` and publishes an `api:message:flag:*` notification.              |
  | `handleMarkUnreadMessage()`   | Marks the channel unread from the message on, with `Channel.markUnread({ message_id })`, and publishes an `api:message:markUnread:*` notification.                  |
  | `handleTogglePinMessage()`    | Pins the message, or unpins it when it's pinned, and publishes an `api:message:pin:*` or `api:message:unpin:*` notification.                                        |
  | `handleToggleMuteUser()`      | Mutes the author, or unmutes them when they're muted, and publishes an `api:user:mute:*` or `api:user:unmute:*` notification.                                       |
  | `handleToggleBanUser()`       | Bans the author with `client.moderation.ban()`, or unbans them when `message.user.banned` is set. No notification. Rejects when the request fails.                  |
  | `handleToggleBlockUser(user)` | Blocks the user, or unblocks them when they're blocked. Logs a failure instead of rejecting.                                                                        |
  | `handleToggleReaction(type)`  | Adds the reaction, or removes it when the current user has already reacted with that type. Logs a failure instead of rejecting.                                     |

  The handlers that publish a notification catch their own errors and publish a `failed` notification, so they resolve either way.

- **Reads:** `messageActionHandlers` from `useMessageContext()`. No store. The handlers read what they act on, such as the mute and block lists, when they run.
- **Re-renders:** when `Message` provides a new context value.
- **Needs:** `Chat`, a `Channel` or a `Thread`, and a `Message`. Outside a `Message` it returns `undefined`.

`Message` builds one set for each message, and the menu and any component that calls this hook share it, so calling it costs nothing. The handlers don't call the `handle*` props of `Channel`: the actions from `useMessageActions()` do. [Message actions in your own components](https://getstream.io/chat/docs/sdk/react-native/v10/ui/customizing/actions/#message-actions-in-your-own-components) explains the difference.

## useMessageActions

Returns the default actions of the message being rendered, as the menu shows them.

```ts
useMessageActions(): {
  banUser: MessageActionType;
  blockUser: MessageActionType;
  copyMessage: MessageActionType;
  deleteForMeMessage: MessageActionType;
  deleteMessage: MessageActionType;
  editMessage: MessageActionType;
  flagMessage: MessageActionType;
  handleReaction: (reactionType: string) => Promise<void>;
  markUnread: MessageActionType;
  muteUser: MessageActionType;
  pinMessage: MessageActionType;
  quotedReply: MessageActionType;
  retry: MessageActionType;
  threadReply: MessageActionType;
  unpinMessage: MessageActionType;
}
```

- **Returns:** one `MessageActionType` for each default action: its `action`, `title`, `icon` and `type`. Each `action` calls the `handle*` prop of `Channel` or `Thread` first, as the menu does, then the handler from `useMessageActionHandlers()`. The titles are translated, and the mute, block and ban titles follow the author's current state.
- **Reads:** the message, messages and channel contexts, and the author's mute state from `client.mutedUsersStore`.
- **Re-renders:** when the message changes, and when the author is muted or unmuted.
- **Needs:** `Chat`, a `Channel` or a `Thread`, and a `Message`. Throws outside them.

The SDK calls it only while a message's menu is open, because building the actions translates every title and subscribes to the author's mute state. Call it in a custom menu, not in every message row. The fields of `MessageActionType` are on [Actions](https://getstream.io/chat/docs/sdk/react-native/v10/ui/customizing/actions/#messageactiontype).

## useMessageOperations

Returns the operations that change messages and reactions in the list the component renders in.

```ts
useMessageOperations(): MessageOperations
```

- **Returns:**
  - `deleteMessage(message, options?)`: deletes the message. `options` is `{ hard: true }` or `{ delete_for_me: true }`. A message that failed to send is only removed from the list, because the server never had it.
  - `retrySendMessage(message)`: sends a failed message again.
  - `sendReaction(type, messageId)` and `deleteReaction(type, messageId)`: add or remove the current user's reaction. A new reaction replaces the user's others when `enforceUniqueReaction` is set on `Channel`.
  - `removeMessage({ id, parent_id? })`: removes the message from the lists and from the offline database, without a request.
  - `updateMessage(message)`: puts a message into the lists without a request: the channel's, unless it's a reply not shown in the channel, and the open thread's when it's a reply.
- **Reads:** the `Channel`, `Thread` and `Chat` contexts. No store. Each operation picks its owner when it runs: the thread for a reply loaded in the open thread, otherwise the channel.
- **Re-renders:** never by itself. The functions keep their identity.
- **Needs:** `Chat`, and a `Channel` or a `Thread`. Throws outside them.

The delete, retry and reaction operations go through `messageOperations` of the thread or the channel, which shows the change at once and rolls it back if the server refuses. They reject when the server refuses the request. `sendReaction` and `deleteReaction` throw before any request when the channel isn't initialized. [Message operations](https://getstream.io/chat/docs/sdk/react-native/v10/state/writing-messages/message-operations/) explains what happens before the server answers.

## useIsMessageReadByOthers

Says whether another member has read a message.

```ts
useIsMessageReadByOthers(params: { message?: LocalMessage }): boolean
```

- **Returns:** `true` when the message is at or before the furthest point any other member has read. The hook compares every message with that one point, so an older message never reports less than a newer one. `false` without a message.
- **Reads:** `channel.messageReceiptsTracker.snapshotStore`, field `lastReadRefByOthers`, of the channel of the `Channel` or `Thread`.
- **Re-renders:** when the answer changes.
- **Needs:** a `Channel` or a `Thread`. Throws outside both.

`MessageStatus` uses it, and `useIsMessageDeliveredToOthers`, for the default read and delivery icons. The tracker is on [Message receipts tracker](https://getstream.io/chat/docs/sdk/react-native/v10/reference/state/message-receipts-tracker/), and the feature on [Read state, unread and receipts](https://getstream.io/chat/docs/sdk/react-native/v10/features/messaging/read-state-unread-and-receipts/).

## useIsMessageDeliveredToOthers

Says whether a message has reached another member.

```ts
useIsMessageDeliveredToOthers(params: { message?: LocalMessage }): boolean
```

- **Returns:** `true` when the message is at or before the furthest point any other member has received. `false` without a message.
- **Reads:** `channel.messageReceiptsTracker.snapshotStore`, field `lastDeliveredRefByOthers`.
- **Re-renders:** when the answer changes.
- **Needs:** a `Channel` or a `Thread`. Throws outside both.

## useMessageReadData

Returns the users who have read a message.

```ts
useMessageReadData(params: { message?: LocalMessage }): UserResponse[]
```

- **Returns:** every user whose read position is at the message or later, the current user included. `[]` without a message.
- **Reads:** `channel.messageReceiptsTracker.snapshotStore`, through `MessageReceiptsTracker.readersForMessage()`.
- **Re-renders:** whenever the tracker publishes, because each call builds a new list.
- **Needs:** a `Channel` or a `Thread`. Throws outside both.

Leave the current user out for a "Seen by" label. For a flag only, use `useIsMessageReadByOthers`, which re-renders less.

## useMessageDeliveredData

Returns the users who have received a message.

```ts
useMessageDeliveredData(params: { message?: LocalMessage }): UserResponse[]
```

- **Returns:** every user who has received the message, readers included, the current user included. `[]` without a message.
- **Reads:** `channel.messageReceiptsTracker.snapshotStore`, through `MessageReceiptsTracker.deliveredForMessage()`.
- **Re-renders:** whenever the tracker publishes.
- **Needs:** a `Channel` or a `Thread`. Throws outside both.

## useFetchReactions

Loads the reactions of a message, a page at a time, and keeps them current.

```ts
useFetchReactions(params: {
  limit?: number;
  message?: LocalMessage;
  reactionType?: string;
  sort?: SortParamRequest[];
}): {
  loading: boolean;
  loadNextPage: () => Promise<void>;
  reactions: ReactionResponse[];
}
```

`limit` is 25 by default. With `reactionType`, only the reactions of that type load.

- **Returns:**
  - `reactions`: the reactions loaded so far.
  - `loadNextPage()`: loads the next page. It does nothing after the last one.
  - `loading`: `true` until the first page loads. It stays `true` when the request fails.
- **Reads:** `client.queryReactionsAndHydrate()`, into a list the hook keeps for the component. It loads the first page when it mounts, and again when the message, the type or the sort changes. It follows `reaction.new`, `reaction.updated` and `reaction.deleted` for the message, and the reactions the offline database returns while offline.
- **Re-renders:** when a page loads, and when a reaction event for the message arrives.
- **Needs:** `Chat`. Throws outside it.

A failed request publishes an `api:message:reactions:fetch:failed` notification. Define `sort` at module scope: a new array on each render loads the first page again. The reactions screen of the message menu uses this hook. [Reactions](https://getstream.io/chat/docs/sdk/react-native/v10/features/messaging/reactions/) shows a list of who reacted built on it.

## useMessageDateSeparator

Returns the date to show in a separator above a message.

```ts
useMessageDateSeparator(params: {
  hideDateSeparators?: boolean;
  message?: LocalMessage;
  previousMessage?: LocalMessage;
}): Date | undefined
```

- **Returns:** the message's `created_at` as a `Date` when it falls on another day than `previousMessage`, or when there's no previous message. `undefined` otherwise, and always with `hideDateSeparators`.
- **Reads:** its params only.
- **Re-renders:** never by itself.
- **Needs:** nothing.

## useMessageGroupStyles

Returns where a message sits in its group of consecutive messages from one user.

```ts
useMessageGroupStyles(params: {
  message: LocalMessage;
  previousMessage?: LocalMessage;
  nextMessage?: LocalMessage;
  dateSeparatorDate?: Date;
  maxTimeBetweenGroupedMessages?: number;
  noGroupByUser?: boolean;
  getMessageGroupStyle: MessagesContextValue["getMessageGroupStyle"];
}): string[]
```

- **Returns:** the group styles from `getMessageGroupStyle`, such as `['top']`, `['middle']`, `['bottom']` or `['single']`. Without a `getMessageGroupStyle`, the SDK's own function. `[]` with `noGroupByUser`.
- **Reads:** its params only.
- **Re-renders:** never by itself.
- **Needs:** nothing.

The message lists use these two hooks for each row. They render `InlineDateSeparator` with the date, and pass the group styles to `Message`. Use them in a message list of your own.

## useTranslatedMessage

Returns a message with its text in the user's language.

```ts
useTranslatedMessage(message?: LocalMessage | MessageResponse): LocalMessage | MessageResponse | undefined
```

- **Returns:** a copy of the message whose `text` is `message.i18n.<userLanguage>_text` when the message has that translation, and the message's own text otherwise. A deleted message keeps its text. `undefined` without a message.
- **Reads:** `userLanguage` from `TranslationContext`.
- **Re-renders:** when the user's language changes.
- **Needs:** `Chat` or `OverlayProvider`. Throws outside both.

`getTranslatedMessage(message, userLanguage)` does the same outside a component, with the language you pass. The SDK's message text, channel preview and pinned messages show the text the same way. See [Message translation](https://getstream.io/chat/docs/sdk/react-native/v10/features/messaging/message-translation/).

## Best practices

- Inside a message, take the handlers from `useMessageContext()` or `useMessageActionHandlers()`. Call `useMessageOperations()` only for an operation the handlers don't cover, or outside a message.
- Call `useMessageActions()` in a custom menu, not in every row.
- Check what the user may do with `useOwnCapabilitiesContext()` before you show an action, so a user never sees a button the server would refuse.
- Wrap `useMessageOperations()` calls in `try`/`catch`, and report the outcome with [`useNotificationApi`](https://getstream.io/chat/docs/sdk/react-native/v10/reference/hooks/notification-hooks/#usenotificationapi). The handlers that publish a notification already do.
- Edit a message in the composer with `setEditingState(message)` from [`useMessageComposerAPIContext()`](https://getstream.io/chat/docs/sdk/react-native/v10/reference/hooks/composer-hooks/#usemessagecomposerapicontext), or `handleEditMessage()`. There's no edit operation.

## Examples

### Mark unread

The handler publishes its own notification, success or failure:

```tsx
import { Pressable, Text } from "react-native";
import {
  useMessageActionHandlers,
  useMessageContext,
  useOwnCapabilitiesContext,
} from "stream-chat-react-native";

export const MarkUnreadButton = () => {
  const { isMyMessage } = useMessageContext();
  const { handleMarkUnreadMessage } = useMessageActionHandlers();
  const { readEvents } = useOwnCapabilitiesContext();

  if (isMyMessage || !readEvents) return null;

  return (
    <Pressable
      accessibilityRole="button"
      onPress={() => void handleMarkUnreadMessage()}
    >
      <Text>Mark unread</Text>
    </Pressable>
  );
};
```

### Reaction buttons

```tsx
import { Pressable, Text, View } from "react-native";
import { useMessageContext } from "stream-chat-react-native";

export const QuickReactions = () => {
  const { handleReaction } = useMessageContext();

  if (!handleReaction) return null;

  return (
    <View style={{ flexDirection: "row" }}>
      <Pressable
        accessibilityRole="button"
        onPress={() => void handleReaction("love")}
      >
        <Text>Love</Text>
      </Pressable>
      <Pressable
        accessibilityRole="button"
        onPress={() => void handleReaction("like")}
      >
        <Text>Like</Text>
      </Pressable>
    </View>
  );
};
```

Use the types of your `supportedReactions`. The defaults are `like`, `haha`, `love`, `wow` and `sad`, and the emoji types of the full picker: see [Reactions](https://getstream.io/chat/docs/sdk/react-native/v10/features/messaging/reactions/).

### Retry a failed message

```tsx
import { Pressable, Text } from "react-native";
import type { LocalMessage } from "stream-chat";
import { useMessageOperations } from "stream-chat-react-native";

export const RetryButton = ({ message }: { message: LocalMessage }) => {
  const { retrySendMessage } = useMessageOperations();

  if (message.status !== "failed") return null;

  return (
    <Pressable
      accessibilityRole="button"
      onPress={() => retrySendMessage(message).catch(() => undefined)}
    >
      <Text>Retry</Text>
    </Pressable>
  );
};
```

A failed retry leaves the message marked `failed`, so the button stays. The [Custom message](https://getstream.io/chat/docs/sdk/react-native/v10/ui-cookbook/message/custom-message/) UI cookbook builds a whole message component on these hooks.

---

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