Reaction components
The reaction components draw a message's reactions and let the user add them:
ReactionSelector: the quick reactions, opened from the smiley button next to a message or from Add reaction in its menu;ReactionSelectorExtendedList: the full list, behind the selector's + button and the dialog's add button;MessageReactions: the reactions on the message, with their counts;MessageReactionsDetail: the dialog that lists who reacted.
Each is an override key, and reactionOptions sets the reactions they offer. Reactions explains how they work together, and how to change the reactions on offer, their style, their order and what a reaction does.
Best practices
- Set
reactionOptionsonce, around the whole app, so the selector, the message and the dialog show the same reactions. - Wrap a replacement for
ReactionSelectorinforwardRef. The key is typed as aforwardRefcomponent, so a plain function doesn't type-check. - Declare
sortReactionsoutside the component. A new comparator on every render sorts the reactions again on every render.
Customization
The SDK renders the ReactionSelector key in a dialog next to the message. From the smiley button it passes no props, from the menu only dialogId, and it never passes a ref. This replacement puts the default selector in a frame of its own:
import { forwardRef } from "react";
import type { ReactionSelectorProps } from "stream-chat-react";
import { ReactionSelector, WithComponents } from "stream-chat-react";
const FramedReactionSelector = forwardRef<
HTMLDivElement,
ReactionSelectorProps
>(function FramedReactionSelector(props, ref) {
return (
<div className="framed-reaction-selector" ref={ref}>
<ReactionSelector {...props} />
</div>
);
});
const overrides = { ReactionSelector: FramedReactionSelector };
export const App = () => (
<WithComponents overrides={overrides}>
{/* the rest of the app */}
</WithComponents>
);Changing what a reaction does replaces the handler the same way.
ReactionSelector props
| Prop | Type | Default | Description |
|---|---|---|---|
dialogId |
string |
ReactionSelector.getDialogId() |
The id of the dialog the selector closes after a pick. |
handleReaction |
(reactionType: string, event: BaseSyntheticEvent) => Promise<void> |
handleReaction from the context |
Adds or removes a reaction. Used by the selector and its extended list. |
own_reactions |
ReactionResponse[] |
message.own_reactions |
The user's reactions, which the selector shows as pressed. |
The selector closes after a pick when the list's closeReactionSelectorOnClick is set. Its + button shows only when reactionOptions has extended reactions.
ReactionSelectorExtendedList takes the same props. It renders nothing without extended reactions. The default isn't exported by name; it's ReactionSelector.ExtendedList.
MessageReactions props
| Prop | Type | Default | Description |
|---|---|---|---|
capLimit |
{ clustered?: number; segmented?: number } |
{ clustered: 5, segmented: 4 } |
How many reaction types show. Segmented reactions not at the top aren't capped. |
flipHorizontalPosition |
boolean |
false |
Adds the str-chat__message-reactions--flipped-horizontally class, to anchor the reactions on the other side. |
handleFetchReactions |
(reactionType?: string, sort?: SortParamRequest[]) => Promise<ReactionResponse[]> |
handleFetchReactions from the context |
Loads the users for the dialog. |
own_reactions |
ReactionResponse[] |
message.own_reactions |
The user's reactions. |
reaction_groups |
Record<string, ReactionGroupResponse> |
message.reaction_groups |
One summary per reaction type: its count and when it was first and last used. The reactions shown come from here. |
reactionDetailsSort |
SortParamRequest[] |
reactionDetailsSort from the context |
The order of users in the dialog. |
reactions |
ReactionResponse[] |
message.latest_reactions |
The recent reactions, used for the names of the users who reacted. |
reverse |
boolean |
false |
Declared, but has no effect. The default MessageUI passes true. |
sortReactions |
ReactionsComparator |
sortReactions from the context, else by first use |
Orders the reaction types. |
verticalPosition |
'top' | 'bottom' | null |
'top' |
Adds the class that puts the reactions above or below the bubble. null adds neither. |
visualStyle |
'clustered' | 'segmented' | null |
'clustered' |
clustered is one button with the total count; segmented is one button per type, with its count. |
Only the reaction types in reactionOptions show, and a message with none of them renders nothing. Clicking the reactions opens MessageReactionsDetail, showing all reactions in the clustered style and the clicked type in the segmented one. A message with more than 1000 reactions doesn't open it.
ReactionsComparator isn't exported. Type a comparator as MessageListProps['sortReactions']; it compares two summaries with reactionType, reactionCount, firstReactionAt, lastReactionAt and isOwnReaction.
MessageReactionsDetail props
MessageReactions renders the dialog with every prop but dialogId, handleReaction, own_reactions and sort.
| Prop | Type | Default | Description |
|---|---|---|---|
reactions |
ReactionSummary[] |
required | The reaction types to list at the top, with their counts and emoji. |
selectedReactionType |
string | null |
required | The type whose users are listed. null lists every user, each with the emoji they picked. |
handleFetchReactions |
(reactionType?: string, sort?: SortParamRequest[]) => Promise<ReactionResponse[]> |
handleFetchReactions from the context |
Loads the users, 25 at a time, up to 1000. |
handleReaction |
(reactionType: string, event: BaseSyntheticEvent) => Promise<void> |
handleReaction from the context |
Used by the add step. Removing the user's own reaction always uses the context's handler. |
onSelectedReactionTypeChange |
(reactionType: string | null) => void |
Called when the user picks a type, and with null when they pick it again. |
|
own_reactions |
ReactionResponse[] |
message.own_reactions |
Passed to the extended list of the add step. |
reactionDetailsSort |
SortParamRequest[] |
from the context, else [{ direction: -1, field: 'created_at' }] |
The order of users, sent with the query. |
reactionGroups |
Record<string, ReactionGroupResponse> |
The summaries, used to go back to all users when the user removes the last reaction of the selected type. | |
sort |
SortParamRequest[] |
Declared, but not read. Use reactionDetailsSort. |
|
totalReactionCount |
number |
Shown as "5 reactions" at the top. Without it, there's no count. | |
dialogId |
string |
Declared through the selector's props, but not read. |
The add button shows only when reactionOptions has extended reactions, and opens ReactionSelectorExtendedList in the dialog. While users load, the dialog renders the LoadingIndicator key, or MessageReactionsDetailLoadingIndicator by default. That skeleton is exported for a dialog of your own.
Related exports
| Export | What it is |
|---|---|
reactionOptions key, ReactionOptions |
The reactions on offer: quick for the selector, extended for the full list. See The reactions on offer. |
defaultReactionOptions |
The default set. |
emojiToUnicode, unicodeToEmoji |
Convert between an emoji and the unicode code of a reaction option. |
mapEmojiMartData(data) |
Turns the @emoji-mart/data set into extended reactions. |
ReactionSelector.getDialogId({ messageId, threadList }), MessageReactionsDetail.getDialogId({ messageId }) |
The ids of the two dialogs, to open or close them through a dialog manager. |
Hooks
useMessageContext()giveshandleReaction,handleFetchReactions,sortReactionsandreactionDetailsSort.useReactionHandler(message)anduseReactionsFetcher(message)build the context's two handlers, for a component outside a message. See Message hooks.useComponentContext()returnsreactionOptionsand the reaction overrides in effect.