type ReactionOptions = Array<{
type: string;
Component: React.ComponentType;
name?: string;
}>;Reactions Customization
In this example, we’ll override the SDK’s default reaction set (defaultReactionOptions) with up/down arrows to simulate voting.
Best Practices
- Keep custom reactions aligned with your product’s interaction model.
- Ensure all supported reaction types are included to avoid degraded UI.
- Provide a full set for
ReactionsListeven if the selector shows a subset. - Prefer custom handlers when changing behavior without replacing UI.
- Test reactions with permissions and moderation policies enabled.
Under the hood, ReactionSelector, ReactionsList, SimpleReactionsList, and ReactionsListModal render emoji components from reactionOptions. Your custom array must match ReactionOptions:
Let’s build a simple list with 'arrow_up' and 'arrow_down' emojis. To override the defaults, pass the list via Channel so default components can read it:
import { Channel } from "stream-chat-react";
const customReactionOptions = [
{
type: "arrow_up",
Component: () => <>⬆️</>,
name: "Upwards Black Arrow",
},
{
type: "arrow_down",
Component: () => <>⬇️</>,
name: "Downwards Black Arrow",
},
];
export const WrappedChannel = ({ children }) => (
<Channel reactionOptions={customReactionOptions}>{children}</Channel>
);If a reaction type is missing from the list, it won’t be registered and can lead to a degraded experience.
You can also pass options directly to the default components (component props override the Channel value):
import { Channel, ReactionsList, ReactionSelector } from "stream-chat-react";
const CustomReactionsList = (props) => (
<ReactionsList {...props} reactionOptions={customReactionOptions} />
);
// ReactionSelector component requires forwarded reference
const CustomReactionSelector = forwardRef((props, ref) => (
<ReactionSelector
{...props}
ref={ref}
reactionOptions={selectorReactionOptions}
/>
));
export const WrappedChannel = ({ children }) => (
<Channel
ReactionsList={CustomReactionsList}
ReactionSelector={CustomReactionSelector}
>
{children}
</Channel>
);ReactionSelector can show a subset of reactions, but ReactionsList should generally have the full set.


Custom Reaction Handler
To adjust behavior without replacing the UI, provide a custom handleReaction:
import { Channel, ReactionSelector } from "stream-chat-react";
const CustomReactionSelector = React.forwardRef((props, ref) => {
const {
message: { own_reactions: ownReactions = [], id: messageId },
} = useMessageContext("CustomReactionSelector");
const { channel } = useChannelStateContext("CustomReactionSelector");
const handleReaction = useCallback(
async (reactionType, event) => {
// your custom logic with default behavior (minus optimistic updates)
console.log({ event });
const hasReactedWithType =
(ownReactions ?? []).some(
(reaction) => reaction.type === reactionType,
) ?? false;
if (hasReactedWithType) {
await channel.deleteReaction(messageId, reactionType);
return;
}
await channel.sendReaction(messageId, { type: reactionType });
},
[channel, ownReactions, messageId],
);
return (
<ReactionSelector {...props} handleReaction={handleReaction} ref={ref} />
);
});
// and then just add it to ComponentContext
export const WrappedChannel = ({ children }) => (
<Channel ReactionSelector={CustomReactionSelector}>{children}</Channel>
);Read More
See Introducing new reactions in the v11 upgrade guide for more options.