# Reactions

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

## What users get

Users react to a message with an emoji, and see who reacted with what. A reaction shows on the message at once, for everyone viewing it.

- **Adding a reaction.** The smiley button next to a message, or **Add reaction** in its menu, opens the reaction selector with the quick reactions. When extended reactions are configured, a **+** button opens the full list. Picking a reaction the user already gave removes it.
- **Reactions on the message.** The reactions sit at the edge of the message bubble with their counts. By default they're clustered into one pill; the segmented style shows one chip per reaction type.
- **Who reacted.** Clicking the reactions opens a dialog that lists the users who reacted, filtered by reaction type or showing all of them. The dialog has its own button to add a reaction.

| Clustered                                                                      | Segmented                                                                      |
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ |
| ![Clustered reactions](https://getstream.io/docs-assets/images/8735f916d01b.png) | ![Segmented reactions](https://getstream.io/docs-assets/images/46efead3ee6a.png) |

## How it works across the layers

### State

A message carries its reactions in three fields:

- `reaction_groups`: one summary per reaction type, with the count and when it was first and last used;
- `latest_reactions`: the most recent reactions, with their users;
- `own_reactions`: the current user's reactions.

The state layer applies `reaction.new`, `reaction.updated` and `reaction.deleted` events to the message, wherever it's shown: the channel's list, a thread, or a thread's parent message.

The message operations of a channel or a thread change reactions optimistically:

- `MessageOperations.addReaction({ messageId, reaction, options })` adds the reaction to the message, then sends the request. With `options: { enforce_unique: true }`, the new reaction replaces the user's other reactions on the message.
- `MessageOperations.deleteReaction({ messageId, type })` removes the user's reaction of that type, then sends the request.

When the server answers, its copy of the message replaces the local one. If the request fails, the change is undone, unless the request was queued to be sent later. See [Message operations](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/message-operations/#optimistic-updates).

### UI

The user picks 👍 on Ben's message:

1. `ReactionSelector` calls `handleReaction("like")` from the message context.
2. The handler checks `own_reactions`. The user hasn't reacted with `like` yet, so it adds the reaction; had they, it would remove it.
3. The message updates in the list at once, and the handler sends the request with the reaction's emoji as `emoji_code`, so push notifications can show it. If the request fails, the message goes back to how it was. The default handler calls `client.sendReaction()` and `client.deleteReaction()` itself, not `messageOperations`, so a reaction made while offline isn't queued.
4. `MessageReactions` re-renders from the message's `reaction_groups` and `own_reactions`, and marks the user's reaction as theirs.

| Part                                            | Component                                  |
| ----------------------------------------------- | ------------------------------------------ |
| The quick reactions                             | `ReactionSelector`                         |
| The full list behind **+**                      | `ReactionSelectorExtendedList`             |
| The reactions on a message                      | `MessageReactions`                         |
| The dialog that lists who reacted               | `MessageReactionsDetail`                   |
| The reactions that are offered, and their emoji | `reactionOptions` in the component context |

The dialog loads the users with `client.queryReactions()`, 25 at a time, up to 1000 reactions. For a message with more than 1000 reactions, the default `MessageReactions` doesn't open the dialog.

## Turning it on or off

Reactions are always available. The `send-reaction` capability decides who can add them: without it, the message actions leave out the smiley button and **Add reaction**. The server derives the capability from the user's role and the channel type's settings, which include `reactions`.

To hide reactions in your UI, remove the `react` actions from the message action set (see [Actions](https://getstream.io/chat/docs/sdk/react/v15/ui/customizing/actions/#message-actions)), and replace `MessageReactions` with a component that renders nothing.

## Customizing

### The reactions on offer

`reactionOptions` in the component context lists the reactions. Set it with `WithComponents`, so the selector, the reactions on a message and the dialog all use the same set. `quick` is the row in the selector; `extended` is the full list behind **+**:

```tsx
import type { ReactionOptions } from "stream-chat-react";
import { emojiToUnicode, WithComponents } from "stream-chat-react";

const reactionOptions: ReactionOptions = {
  quick: {
    arrow_up: {
      Component: () => <>⬆️</>,
      name: "Up vote",
      unicode: emojiToUnicode("⬆️"),
    },
    arrow_down: {
      Component: () => <>⬇️</>,
      name: "Down vote",
      unicode: emojiToUnicode("⬇️"),
    },
  },
  extended: {
    fire: {
      Component: () => <>🔥</>,
      name: "Fire",
      unicode: emojiToUnicode("🔥"),
    },
    rocket: {
      Component: () => <>🚀</>,
      name: "Rocket",
      unicode: emojiToUnicode("🚀"),
    },
  },
};

export const App = () => (
  <WithComponents overrides={{ reactionOptions }}>
    {/* the rest of the app */}
  </WithComponents>
);
```

Each key is the reaction `type` stored on the server. The handler turns `unicode` back into the emoji and sends it as `emoji_code`; leave it out and push notifications have no emoji to show. To offer the whole emoji set as extended reactions, `mapEmojiMartData(data)` turns the `@emoji-mart/data` set into an `extended` map.

![Messages with up-vote and down-vote reactions from a custom reaction set](https://getstream.io/docs-assets/images/ad51339008a9.png)

### Style and position

`MessageReactions` takes `visualStyle` (`"clustered"` or `"segmented"`) and `verticalPosition` (`"top"` or `"bottom"`). Replace it with a version that sets them:

```tsx
import type { MessageReactionsProps } from "stream-chat-react";
import { MessageReactions, WithComponents } from "stream-chat-react";

const SegmentedReactions = (props: MessageReactionsProps) => (
  <MessageReactions
    {...props}
    verticalPosition="bottom"
    visualStyle="segmented"
  />
);

export const App = () => (
  <WithComponents overrides={{ MessageReactions: SegmentedReactions }}>
    {/* the rest of the app */}
  </WithComponents>
);
```

Segmented reactions at the top show at most four chips and a counter for the rest. At the bottom, every reaction type shows.

### Order

By default, reaction types are in the order they were first used on the message. Pass `sortReactions` to `MessageList` or `VirtualizedMessageList` to change it:

```tsx
import type { MessageListProps } from "stream-chat-react";
import { MessageList } from "stream-chat-react";

const byCount: MessageListProps["sortReactions"] = (a, b) =>
  b.reactionCount - a.reactionCount;

export const SortedMessageList = () => <MessageList sortReactions={byCount} />;
```

`reactionDetailsSort` on the same lists sets the order of users in the dialog. It's sent to the server with the query, and defaults to the newest first.

### Changing what a reaction does

`ReactionSelector` takes a `handleReaction` prop that replaces the message's handler. This selector allows one reaction per user, through the state layer's `messageOperations`. The `ReactionSelector` override key is typed as a `forwardRef` component, so wrap the replacement in `forwardRef`:

```tsx
import { forwardRef } from "react";
import type { ReactionSelectorProps } from "stream-chat-react";
import {
  ReactionSelector,
  useChannel,
  useMessageContext,
  useThreadContext,
  WithComponents,
} from "stream-chat-react";

// One reaction per user: a new reaction replaces the previous one.
const SingleReactionSelector = forwardRef<
  HTMLDivElement,
  ReactionSelectorProps
>(function SingleReactionSelector(props, _ref) {
  const { message } = useMessageContext();
  const channel = useChannel();
  const thread = useThreadContext();
  const { messageOperations } = thread ?? channel;

  const handleReaction = async (type: string) => {
    const alreadyReacted = message.own_reactions?.some(
      (reaction) => reaction.type === type,
    );
    if (alreadyReacted) {
      await messageOperations.deleteReaction({ messageId: message.id, type });
      return;
    }
    await messageOperations.addReaction({
      messageId: message.id,
      options: { enforce_unique: true },
      reaction: { type },
    });
  };

  return <ReactionSelector {...props} handleReaction={handleReaction} />;
});

export const App = () => (
  <WithComponents overrides={{ ReactionSelector: SingleReactionSelector }}>
    {/* the rest of the app */}
  </WithComponents>
);
```

The handler covers the selector and its extended list. The add step in the reactions dialog still uses the message's handler, unless you also replace `MessageReactionsDetail`.

### The reactions dialog

`MessageReactionsDetail` is an override key. If you rebuild it, keep the default behaviour:

- `selectedReactionType={null}` means "show all reactions". Clicking the selected type again goes back to it. With no type selected, each user's row also shows the emoji they picked.
- The clustered `MessageReactions` opens the dialog showing all reactions; a segmented chip opens it filtered to its type.
- The add button switches the dialog to `ReactionSelectorExtendedList`.
- `MessageReactionsDetailLoadingIndicator` is the default loading skeleton, ready to reuse.

`ReactionSelector`, `ReactionSelectorExtendedList` and `MessageReactions` are override keys too. See [Overriding components](https://getstream.io/chat/docs/sdk/react/v15/ui/customizing/overriding-components/).

## Reference

- State: [Message operations](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/message-operations/) and [What's optimistic and what isn't](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/whats-optimistic-and-what-isnt/#message-operations).
- UI: [Message list UI](https://getstream.io/chat/docs/sdk/react/v15/ui/components/message-list-ui/#a-message) and [Overriding components](https://getstream.io/chat/docs/sdk/react/v15/ui/customizing/overriding-components/).
- Props: [Reaction components](https://getstream.io/chat/docs/sdk/react/v15/component-reference/message/reaction-components/), [MessageContext](https://getstream.io/chat/docs/sdk/react/v15/component-reference/message/messagecontext/) and [MessageList](https://getstream.io/chat/docs/sdk/react/v15/component-reference/message-list/messagelist/).
- Hooks: [Message hooks](https://getstream.io/chat/docs/sdk/react/v15/reference/hooks/message-hooks/).

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/v15/features/messaging/reactions/](https://getstream.io/chat/docs/sdk/react/v15/features/messaging/reactions/).