# Message hooks

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

These hooks build the handlers of one message: its actions, its thread, its reactions, its mentions and its author. They're exported from `stream-chat-react`.

`Message` calls them for the message it renders and puts the results in `MessageContext`. Inside a custom `MessageUI`, read the handlers from `useMessageContext()` instead of calling the hooks again:

| Hook                   | In `MessageContext`                                   |
| ---------------------- | ----------------------------------------------------- |
| `useActionHandler`     | `handleAction`                                        |
| `useDeleteHandler`     | `handleDelete`                                        |
| `useFlagHandler`       | `handleFlag`                                          |
| `useMarkUnreadHandler` | `handleMarkUnread`                                    |
| `useMuteHandler`       | `handleMute`                                          |
| `usePinHandler`        | `handlePin`                                           |
| `useRetryHandler`      | `handleRetry`, which takes the message                |
| `useOpenThreadHandler` | `handleOpenThread`                                    |
| `useReactionHandler`   | `handleReaction`                                      |
| `useReactionsFetcher`  | `handleFetchReactions`                                |
| `useMentionsHandler`   | `onMentionsClickMessage` and `onMentionsHoverMessage` |
| `useUserHandler`       | `onUserClick` and `onUserHover`                       |

The context's other values are on [MessageContext](https://getstream.io/chat/docs/sdk/react/v15/component-reference/message/messagecontext/), and the default actions that call these handlers on [Actions](https://getstream.io/chat/docs/sdk/react/v15/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/v15/reference/hooks/reminder-hooks/), and `useMessageTextStreaming` on [AI hooks](https://getstream.io/chat/docs/sdk/react/v15/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.

## useDeleteHandler

Returns a function that deletes the message.

```ts
useDeleteHandler(message?: LocalMessage, notifications?: DeleteMessageNotifications): (options?: DeleteMessageOptions) => Promise<void> | void
```

- **Returns:** a function that deletes the message with `MessageOperations.delete()`, the thread's inside a `Thread`, otherwise the channel's. The message shows as deleted at once, and comes back if the server refuses. `options` are the request's, `{ hard?, delete_for_me? }`. The function doesn't reject: on an error it calls `notifications.notify` when you pass one, and resolves.
- **Reads:** no store.
- **Re-renders:** never by itself.
- **Needs:** `Chat`, and a `Channel` or a `Thread`. Throws outside them.

What a delete changes before the server answers is on [What's optimistic and what isn't](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/whats-optimistic-and-what-isnt/).

## useFlagHandler

Returns a click handler that flags the message for moderation.

```ts
useFlagHandler(message?: LocalMessage): ReactEventHandler
```

- **Returns:** `(event) => Promise<void>`, which calls `client.moderation.flagMessage({ message_id: message.id })`. It rejects when the request fails.
- **Reads:** no store.
- **Re-renders:** never by itself.
- **Needs:** `Chat`. Throws outside it.

## useMarkUnreadHandler

Returns a click handler that marks the channel unread from the message on.

```ts
useMarkUnreadHandler(message?: LocalMessage, notifications?: MarkUnreadHandlerNotifications): ReactEventHandler
```

- **Returns:** `(event) => Promise<void>`, which calls `Channel.markUnread({ message_id })`. It rejects when the request fails. After a success it calls `notifications.notify` with the text of `getSuccessNotification`, when you pass both.
- **Reads:** no store. The read state changes when the `notification.mark_unread` event arrives.
- **Re-renders:** never by itself.
- **Needs:** a `Channel` or a `Thread`. Throws outside both.

## useMuteHandler

Returns a click handler that mutes the message's author, or unmutes them when they're muted.

```ts
useMuteHandler(message?: LocalMessage, notifications?: MuteUserNotifications): ReactEventHandler
```

- **Returns:** `(event) => Promise<void>`. It checks `client.mutedUsersStore` and calls `client.moderation.mute()` or `client.moderation.unmute()` for `message.user`. It doesn't reject: on success or error it calls `notifications.notify` when you pass one, and resolves.
- **Reads:** `client.mutedUsersStore`, once per click.
- **Re-renders:** never by itself.
- **Needs:** `Chat`. Throws outside it.

## usePinHandler

Returns a click handler that pins the message, or unpins it when it's pinned.

```ts
usePinHandler(message: LocalMessage, notifications?: PinMessageNotifications): { handlePin: ReactEventHandler }
```

- **Returns:** `{ handlePin }`. `handlePin(event)` shows the message as pinned or unpinned at once, then calls `client.pinMessage()` or `client.unpinMessage()`. On an error it puts the message back as it was and calls `notifications.notify` when you pass one. It doesn't reject.
- **Reads:** no store. It writes to the message list of the thread or the channel.
- **Re-renders:** never by itself.
- **Needs:** `Chat`, and a `Channel` or a `Thread`. Throws outside them.

## useRetryHandler

Returns a function that sends a failed message again.

```ts
useRetryHandler(): RetryHandler
```

- **Returns:** `({ localMessage, options? }) => Promise<void>`, which calls `MessageOperations.retry()` of the thread or the channel. It resends the request that failed, and rejects when the retry fails too. The message stays in the list either way, marked `failed` again after a failure.
- **Reads:** no store.
- **Re-renders:** when the `Thread` or the `Channel` above changes.
- **Needs:** a `Channel` or a `Thread`. Throws outside both.

It takes no message: pass it when you call the function. `handleRetry` in `MessageContext` takes the message itself and calls this function with `{ localMessage: message }`.

## useActionHandler

Returns the handler for the buttons of a message with actions, such as the Send, Shuffle and Cancel buttons of a `/giphy` preview.

```ts
useActionHandler(message?: LocalMessage): ActionHandlerReturnType
```

- **Returns:** `(dataOrName?, value?, event?) => Promise<void> | void`. It sends the chosen action with `Channel.sendAction(message.id, data)`. Pass the action's `name` and `value`, or an object of them, such as `{ image_action: "shuffle" }`. When the server returns a message, it replaces the one in the list; otherwise the message is removed.
- **Reads:** no store. It writes to the message list of the thread or the channel.
- **Re-renders:** never by itself.
- **Needs:** a `Channel` or a `Thread`. Throws outside both.

`Attachment` receives it as `actionHandler`, and renders the buttons of the message's actions with it.

## useOpenThreadHandler

Returns a click handler that opens the message's thread.

```ts
useOpenThreadHandler(message?: LocalMessage, customOpenThread?: (message: LocalMessage, event: React.BaseSyntheticEvent) => void): ReactEventHandler
```

- **Returns:** `(event) => void`. It calls `customOpenThread` when you pass one. Otherwise it calls `openThread({ channel, message })` from `useWorkspaceNavigation()`.
- **Reads:** the channel from `useChannel()`, and the workspace navigation. No store.
- **Re-renders:** when the channel or the workspace navigation changes.
- **Needs:** a `Channel` or a `Thread`. Throws outside both. Without a layout that opens threads, such as `ChatView`, the default does nothing: pass `customOpenThread` to open the thread yourself.

Where a thread opens is on [Moving between channels, threads and messages](https://getstream.io/chat/docs/sdk/react/v15/ui/layout/moving-between-channels-threads-and-messages/).

## useMessageAlsoSentInChannelNavigation

Returns the navigation behind the "Also sent in channel" and "Replied to a thread" labels of a reply.

```ts
useMessageAlsoSentInChannelNavigation(): MessageAlsoSentInChannelNavigation
```

- **Returns:**
  - `viewReference()`: in a thread, jumps to the reply in the channel. In a channel, opens the reply's thread and jumps to the reply there.
  - `viewReplyInChannel(messageId?)` and `viewReplyInThread(replyId?, parentId?)`: each half on its own. Both default to the message in context.
  - `isInThread`: whether the component renders inside a `Thread`.
  - `isShownInChannel`: whether the reply was also sent to the channel, `message.show_in_channel`.
- **Reads:** the message, channel, thread and workspace navigation contexts. No store.
- **Re-renders:** when one of those contexts changes.
- **Needs:** `Chat`, a `Channel` or a `Thread`, and a `Message`. Throws outside them.

When the thread can't be loaded, it publishes an `api:message:search:not-found` notification. `MessageAlsoSentInChannelIndicator` uses it.

## useReactionHandler

Returns a function that adds a reaction, or removes it when the current user has already reacted with that type.

```ts
useReactionHandler(message?: LocalMessage): (reactionType: string, event?: React.BaseSyntheticEvent) => Promise<void>
```

- **Returns:** a function that takes a reaction type such as `"like"`. It shows the change at once, then calls `client.sendReaction()` or `client.deleteReaction()`. On an error it puts the message back as it was, and resolves.
- **Reads:** `reactionOptions` from `ComponentContext`, for the emoji code sent with the reaction. It writes to the message list of the thread or the channel.
- **Re-renders:** never by itself.
- **Needs:** `Chat`, and a `Channel` or a `Thread`. Throws outside them.

## useReactionsFetcher

Returns a function that loads the reactions of the message.

```ts
useReactionsFetcher(message: LocalMessage): (reactionType?: string, sort?: SortParamRequest[]) => Promise<ReactionResponse[]>
```

- **Returns:** a function that loads the message's reactions with `client.queryReactions()`, 25 a request, up to 1000. Pass a type to load only that type. It rejects when a request fails.
- **Reads:** no store. The reactions aren't kept: each call loads them again.
- **Re-renders:** never by itself.
- **Needs:** `Chat`. Throws outside it.

`MessageReactionsDetail`, the list of who reacted, calls it as `handleFetchReactions`.

## useMentionsHandler

Returns click and hover handlers for the mentions in the message's text.

```ts
useMentionsHandler(message?: LocalMessage, customMentionHandler?: { onMentionsClick?: CustomMentionHandler; onMentionsHover?: CustomMentionHandler }): { onMentionsClick: ReactEventHandler; onMentionsHover: ReactEventHandler }
```

- **Returns:** `onMentionsClick` and `onMentionsHover`. Each calls your handler with `(event, mentionedUsers, message)`, and only when the message mentions someone: users, roles, groups, the channel or `@here`. Without your handler they do nothing.
- **Reads:** no store.
- **Re-renders:** never by itself.
- **Needs:** nothing.

`Message` passes its `onMentionsClick` and `onMentionsHover` props here.

## useUserHandler

Returns click and hover handlers for the message's author.

```ts
useUserHandler(message?: LocalMessage, eventHandlers?: { onUserClickHandler?: UserEventHandler; onUserHoverHandler?: UserEventHandler }): { onUserClick: ReactEventHandler; onUserHover: ReactEventHandler }
```

- **Returns:** `onUserClick` and `onUserHover`. Each calls your handler with `(event, message.user)`. Without your handler, or a message without a user, they do nothing.
- **Reads:** no store.
- **Re-renders:** never by itself.
- **Needs:** nothing.

`Message` passes its `onUserClick` and `onUserHover` props here. The default `MessageUI` attaches the results to the author's avatar.

## useUserRole

Returns what the current user may do with the message.

```ts
useUserRole(message: LocalMessage, disableQuotedMessages?: boolean): { canBlockUser: boolean; canDelete: boolean; canEdit: boolean; canFlag: boolean; canMarkUnread: boolean; canMute: boolean; canPin: boolean; canQuote: boolean; canReact: boolean; canReply: boolean; canSendMessage: boolean; isMyMessage: boolean; isAdmin: boolean; isModerator: boolean; isOwner: boolean }
```

- **Returns:** a flag for each action, worked out from the channel's capabilities and the message's author:
  - `canEdit`: `update-any-message`, or `update-own-message` on your own message. Never for a poll or a `/giphy` message.
  - `canDelete`: `delete-any-message`, or `delete-own-message` on your own message.
  - `canFlag`, `canMute` and `canMarkUnread`: only on someone else's message, with `flag-message`, `mute-channel` and `read-events`.
  - `canBlockUser`: on someone else's message.
  - `canPin`, `canReact`, `canReply` and `canSendMessage`: `pin-message`, `send-reaction`, `send-reply` and `send-message`.
  - `canQuote`: `quote-message`, unless `disableQuotedMessages` is `true`.
  - `isMyMessage`: whether the current user wrote it.
  - `isAdmin`, `isModerator` and `isOwner` are deprecated: they read roles, not capabilities.
- **Reads:** `channel.state`, field `ownCapabilities`, through `useChannelCapabilities`.
- **Re-renders:** when the channel's capabilities change.
- **Needs:** `Chat`, and a `Channel` or a `Thread`. Throws outside them.

## useBaseMessageActionSetFilter

Removes from an action set the actions that don't apply to the message in context.

```ts
useBaseMessageActionSetFilter(messageActionSet: MessageActionSetItem[], disable?: boolean): MessageActionSetItem[]
```

- **Returns:** the actions that apply. It drops every action for a bounced, system, ephemeral or sending message. Otherwise it drops each action the user may not use according to `useUserRole`, the ones that don't work on a thread reply, the ones with nothing to act on, such as Copy on a message without text, and Remind me and Save for later when reminders are off. A failed message keeps only Resend, Edit and Delete. With `disable`, it returns the set as it is.
- **Reads:** the message from `useMessageContext()`, `useUserRole()`, and `channel.configState` for whether reminders are on.
- **Re-renders:** when the message, the capabilities or the reminder setting changes.
- **Needs:** `Chat`, a `Channel` or a `Thread`, and a `Message`. Throws outside them.

`MessageActions` runs it on its `messageActionSet`. [Message actions](https://getstream.io/chat/docs/sdk/react/v15/ui/customizing/actions/#message-actions) explains when to run it yourself.

## Best practices

- Inside a message, take the handlers from `useMessageContext()`. Call a hook yourself only in a component that replaces `Message`, or that has a message but no `MessageContext`.
- Show an action only when `useUserRole` allows it, so a user never sees a button the server would refuse.
- Wrap `handleFlag`, `handleMarkUnread` and retries in `try`/`catch`, and report the outcome with [`useNotificationApi`](https://getstream.io/chat/docs/sdk/react/v15/reference/hooks/notification-hooks/#usenotificationapi), as the default Flag and Mark unread actions do. Delete, mute, pin and reactions catch their own errors and resolve either way, so a `catch` around them never runs.
- Edit a message in the composer: call `MessageComposer.initState({ composition: message })` on the composer from [`useMessageComposerController()`](https://getstream.io/chat/docs/sdk/react/v15/reference/hooks/composer-hooks/#usemessagecomposercontroller). There's no edit hook.

## Examples

### Mark unread with a notification

```tsx
import {
  useMessageContext,
  useNotificationApi,
  useUserRole,
} from "stream-chat-react";

export const MarkUnreadButton = () => {
  const { handleMarkUnread, message } = useMessageContext();
  const { canMarkUnread } = useUserRole(message);
  const { addNotification } = useNotificationApi();

  if (!canMarkUnread) return null;

  return (
    <button
      onClick={async (event) => {
        try {
          await handleMarkUnread(event);
        } catch (error) {
          addNotification({
            emitter: "MarkUnreadButton",
            error: error instanceof Error ? error : undefined,
            message: "The message could not be marked unread",
            severity: "error",
            type: "api:message:markUnread:failed",
          });
        }
      }}
      type="button"
    >
      Mark unread
    </button>
  );
};
```

### Reaction buttons

```tsx
import { useMessageContext } from "stream-chat-react";

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

  return (
    <>
      <button onClick={(event) => handleReaction("love", event)} type="button">
        Love
      </button>
      <button onClick={(event) => handleReaction("like", event)} type="button">
        Like
      </button>
    </>
  );
};
```

Use the types of your reaction options. The defaults are `haha`, `like`, `love`, `sad`, `wow` and `fire`: see [Reactions](https://getstream.io/chat/docs/sdk/react/v15/features/messaging/reactions/).

### Retry a failed message

```tsx
import type { LocalMessage } from "stream-chat";
import { useRetryHandler } from "stream-chat-react";

export const RetryButton = ({ message }: { message: LocalMessage }) => {
  const retry = useRetryHandler();

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

  return (
    <button
      onClick={() => retry({ localMessage: message }).catch(() => undefined)}
      type="button"
    >
      Retry
    </button>
  );
};
```

A failed retry leaves the message marked `failed`, so the button stays.

### Actions a user may take

```tsx
import { useMessageContext, useUserRole } from "stream-chat-react";

export const MessagePermissions = () => {
  const { message } = useMessageContext();
  const { canDelete, canEdit, canReply } = useUserRole(message);

  return (
    <ul>
      {canEdit && <li>Edit</li>}
      {canDelete && <li>Delete</li>}
      {canReply && <li>Reply</li>}
    </ul>
  );
};
```

The [Custom message](https://getstream.io/chat/docs/sdk/react/v15/recipes/message/custom-message/) recipe builds a whole `MessageUI` on these handlers.

---

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