Info:
This is release candidate documentation for Stream Chat React SDK v15. For the latest stable version, see the latest version (v14).

Message actions

Message actions are what a user can do with one message: react to it, reply to it, quote it, pin it, edit it, delete it and more. This page lists the actions the SDK ships, what each one does and who sees it. To add, remove or replace actions, see Actions.

What users get

When a user points at a message or moves focus into it, buttons appear beside it: one to add a reaction, one to reply in a thread, and a "more" button that opens the message menu.

Say the user opens the menu on a message from Bob. They see Add reaction, Thread Reply, Quote Reply, Pin, Copy Message, Mark as unread, Remind me, Save for later, Flag, Mute and Block User. On their own message they see Edit Message and Delete message instead of Mark as unread, Flag, Mute and Block User. Most actions confirm the result with a notification, such as "Message pinned" or "Saved for later".

How it works across the layers

MessageUI renders MessageActions on every message that isn't deleted. MessageActions takes an action set, defaultMessageActionSet unless you pass another one. Each item in the set has three fields:

  • type names the action, for example pin;
  • placement puts it next to the message (quick) or in the menu (dropdown); one item with the placement quick-dropdown-toggle is the "more" button;
  • Component renders it.

Before rendering, useBaseMessageActionSetFilter drops the actions the user can't take on this message. The remaining quick actions render beside the message, and the dropdown actions render in the menu.

Each action calls the state layer. The table lists the default set in menu order, with what each action calls and when the filter keeps it. "Own" means the connected user sent the message, and names in code font such as send-reaction are channel capabilities.

Action type What it calls Shown when
Add reaction (quick and in the menu) react Opens the reaction selector send-reaction
Reply (quick), Thread Reply (menu) reply Opens the message's thread through useWorkspaceNavigation().openThread send-reply, and the message isn't a thread reply
Quote Reply quote MessageComposer.setQuotedMessage(message) quote-message
Download Attachment download Downloads the file, or opens a submenu when there are several The message has a file, image, video, audio or voice recording
Pin, Unpin pin client.pinMessage(message), client.unpinMessage(message) The message isn't a thread reply
Copy Message copyMessageText Copies message.text to the clipboard The message has text
Resend resendMessage MessageOperations.retry The message failed to send and can be retried, and send-message
Edit Message edit Loads the message into the composer update-any-message, or update-own-message on an own message; never a poll or a Giphy
Mark as unread markUnread Channel.markUnread({ message_id }) Not own, read-events, and the message isn't a thread reply
Remind me, Remove reminder remindMe client.reminders.upsertReminder, client.reminders.deleteReminder Reminders are on for the channel, and the message isn't saved for later
Save for later, Remove save for later saveForLater client.reminders.createReminder, client.reminders.deleteReminder Reminders are on for the channel, and the message has no scheduled reminder
Flag flag client.moderation.flagMessage({ message_id: message.id }) Not own, and flag-message
Mute, Unmute mute client.moderation.mute or client.moderation.unmute for the author Not own, and mute-channel
Delete message delete Asks to confirm, then MessageOperations.delete delete-any-message, or delete-own-message on an own message; the message isn't deleted already
Block User, Unblock blockUser client.blockUser, client.unblockUser for the author Not own

Some messages get no actions at all: a message that is still sending, a system or ephemeral message, and a bounced message. A message that failed to send keeps at most Resend, Edit Message and Delete message, and it keeps Edit Message and Delete message only when the failure was a network error.

A few things the table implies:

  • Updates on screen. Reactions, deletes and resends change the message list before the server answers. Pinning doesn't at the state layer, but the Pin action updates the message on screen at once and puts it back if the request fails. What's optimistic and what isn't has the full list.
  • Notifications. Pin, Mark as unread, the reminder actions, Flag, Mute and Delete report their result through client.notifications. See Notifications.
  • Layout. Reply and Thread Reply open the thread through the layout. Without ChatView, or a WorkspaceNavigationProvider of your own, they do nothing. See Moving between channels, threads and messages.

Each action has its own feature page: Reactions, Replies and threads, Quoted replies, Pinned messages, Sending, editing and deleting, Read state, unread and receipts, Reminders and save for later and Flagging, muting, blocking and bans.

Turning it on or off

Message actions are always on. Who sees which action depends on two things:

  • Capabilities. The server gives the connected user a set of capabilities on each channel, derived from their roles and the channel type's permissions. The filter reads them for every message. Change them on the server, see Permissions reference.
  • Channel settings. Remind me and Save for later also need channel.config.userMessageReminders.enabled, see Reminders and save for later.

The filter doesn't check a capability for Pin, Download or Block User. Pin shows even for users without the pin-message capability, and the server rejects their request. Pinned messages shows how to hide it from them.

To remove the actions from every message, replace MessageActions with a component that renders nothing:

import type { Channel as ChannelType } from "stream-chat";
import { Channel, WithComponents } from "stream-chat-react";

const NoMessageActions = () => null;

export const ChannelWithoutActions = ({
  channel,
}: {
  channel: ChannelType;
}) => (
  <WithComponents overrides={{ MessageActions: NoMessageActions }}>
    <Channel channel={channel}>{/* MessageList, MessageComposer */}</Channel>
  </WithComponents>
);

Customizing

To change the actions, pass your own action set to MessageActions and register the wrapper with WithComponents. Start from defaultMessageActionSet to keep the built-in actions. The base filter runs on your set too, so the actions you keep stay hidden from users who can't take them.

This example removes Block User and keeps everything else:

import type { Channel as ChannelType } from "stream-chat";
import {
  Channel,
  MessageActions,
  WithComponents,
  defaultMessageActionSet,
} from "stream-chat-react";

// Keep the "more" button, which has no type, and every action except blocking.
const messageActionSet = defaultMessageActionSet.filter(
  (action) =>
    action.placement === "quick-dropdown-toggle" || action.type !== "blockUser",
);

const CustomMessageActions = () => (
  <MessageActions messageActionSet={messageActionSet} />
);

export const ChannelWithCustomActions = ({
  channel,
}: {
  channel: ChannelType;
}) => (
  <WithComponents overrides={{ MessageActions: CustomMessageActions }}>
    <Channel channel={channel}>{/* MessageList, MessageComposer */}</Channel>
  </WithComponents>
);

Actions explains how action sets work and how to add quick and dropdown actions of your own. Custom message actions walks through a complete example. Message translation adds a Translate action.

Reference

  • Message UI components: the MessageActions props, messageActionSet and disableBaseMessageActionSetFilter.
  • ComponentContext: the MessageActions override key.
  • Message hooks: the handlers behind the actions, such as usePinHandler and useMarkUnreadHandler.
  • Actions: every action set in the SDK.