await chat.messages.addReaction('msg_3xRโฆ', '๐');
await chat.messages.removeReaction('msg_3xRโฆ', '๐');
chat.on('reactionAdded', (event) => {
console.log(`${event.userId} reacted ${event.emoji}`);
});const { add, remove, pending } = useReactions();
await add('msg_3xRโฆ', '๐');pending is true while a reaction call is in flight โ disable the
button on it rather than letting a double-tap fire twice.
await raven.chat!.messages.addReaction('msg_3xRโฆ', '๐');
await raven.chat!.messages.removeReaction('msg_3xRโฆ', '๐');await chat.addReaction(messageId, '๐');
await chat.removeReaction(messageId, '๐');
chat.reactions.listen((event) {
print('${event.userId} reacted ${event.emoji}');
});Idempotent in both directions
Adding the same reaction twice is a no-op, not a duplicate. Removing one
that isn't there succeeds. Both are guaranteed by a unique constraint on
(messageId, userId, emoji), so a double-tap on a flaky connection is
harmless โ and reactions are the most double-tapped control in any chat
UI, often clicked on exactly the connection quality where a request
might get retried.
Grouped, not raw
Messages carry reactions already grouped per emoji:
"reactions": [
{ "emoji": "๐", "count": 3, "userIds": ["alice", "bob", "carol"] },
{ "emoji": "๐", "count": 1, "userIds": ["dave"] }
]Clients want "๐ ร3 (alice, bob, carol)", not three rows to group
themselves โ doing it server-side means every transport agrees on the
shape. userIds lets you render "you reacted" state and a hover tooltip
without a second request.
Limits
Up to 200 reactions per message, and 32 characters per emoji โ enough for any real emoji, including multi-codepoint sequences like ๐จโ๐ฉโ๐งโ๐ฆ, and short enough that the field can't become a second message body.
Raven doesn't validate that a reaction is an emoji. Products use
custom reactions, :shipit:-style shortcodes, and image keys โ an
allow-list would break all of them to prevent nothing.
Common errors
| Error | Why | Fix |
|---|---|---|
| Reaction silently doesn't appear twice | Idempotent by design โ the second addReaction for the same (message, user, emoji) is a no-op, not an error. | Expected โ read it as success, not a bug. |
INVALID_MESSAGE/validation error on an over-length emoji field | Over the 32-character limit. | Cap custom shortcode length client-side before sending. |
Production notes
- Render from the message's own
reactionsarray, not a locally accumulated count โ a reconnect replayingreactionAddedevents you already counted would double them. - Disable a reaction button while its own call is pending (React:
useReactions()'spending) rather than relying on idempotency alone to absorb a rapid double-tap.
Related
- Messages โ reactions live on the message object itself.
- Live Streaming โ Reactions โ the same mechanism, aggregated onto one root message per stream.