stream-chat-react
Version:
React components to create chat conversations or livestream style chat
120 lines (119 loc) • 5.23 kB
JavaScript
import { useCallback } from 'react';
import throttle from 'lodash.throttle';
import { useThreadContext } from '../../Threads';
import { useChannelActionContext } from '../../../context/ChannelActionContext';
import { useChannelStateContext } from '../../../context/ChannelStateContext';
import { useChatContext } from '../../../context/ChatContext';
export const reactionHandlerWarning = `Reaction handler was called, but it is missing one of its required arguments.
Make sure the ChannelAction and ChannelState contexts are properly set and the hook is initialized with a valid message.`;
export const useReactionHandler = (message) => {
const thread = useThreadContext();
const { updateMessage } = useChannelActionContext('useReactionHandler');
const { channel, channelCapabilities } = useChannelStateContext('useReactionHandler');
const { client } = useChatContext('useReactionHandler');
const createMessagePreview = useCallback((add, reaction, message) => {
const newReactionGroups = message?.reaction_groups || {};
const reactionType = reaction.type;
const hasReaction = !!newReactionGroups[reactionType];
if (add) {
const timestamp = new Date().toISOString();
newReactionGroups[reactionType] = hasReaction
? {
...newReactionGroups[reactionType],
count: newReactionGroups[reactionType].count + 1,
}
: {
count: 1,
first_reaction_at: timestamp,
last_reaction_at: timestamp,
sum_scores: 1,
};
}
else {
if (hasReaction && newReactionGroups[reactionType].count > 1) {
newReactionGroups[reactionType] = {
...newReactionGroups[reactionType],
count: newReactionGroups[reactionType].count - 1,
};
}
else {
delete newReactionGroups[reactionType];
}
}
const newReactions = add
? [reaction, ...(message?.latest_reactions || [])]
: message.latest_reactions?.filter((item) => !(item.type === reaction.type && item.user_id === reaction.user_id));
const newOwnReactions = add
? [reaction, ...(message?.own_reactions || [])]
: message?.own_reactions?.filter((item) => item.type !== reaction.type);
return {
...message,
latest_reactions: newReactions || message.latest_reactions,
own_reactions: newOwnReactions,
reaction_groups: newReactionGroups,
};
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[client.user, client.userID]);
const createReactionPreview = (type) => ({
message_id: message?.id,
score: 1,
type,
user: client.user,
user_id: client.user?.id,
});
const toggleReaction = throttle(async (id, type, add) => {
if (!message || !channelCapabilities['send-reaction'])
return;
const newReaction = createReactionPreview(type);
const tempMessage = createMessagePreview(add, newReaction, message);
try {
updateMessage(tempMessage);
thread?.upsertReplyLocally({ message: tempMessage });
const messageResponse = add
? await channel.sendReaction(id, { type })
: await channel.deleteReaction(id, type);
// seems useless as we're expecting WS event to come in and replace this anyway
updateMessage(messageResponse.message);
}
catch (error) {
// revert to the original message if the API call fails
updateMessage(message);
thread?.upsertReplyLocally({ message });
}
}, 1000);
return async (reactionType, event) => {
if (event?.preventDefault) {
event.preventDefault();
}
if (!message) {
return console.warn(reactionHandlerWarning);
}
let userExistingReaction = null;
if (message.own_reactions) {
message.own_reactions.forEach((reaction) => {
// own user should only ever contain the current user id
// just in case we check to prevent bugs with message updates from breaking reactions
if (reaction.user &&
client.userID === reaction.user.id &&
reaction.type === reactionType) {
userExistingReaction = reaction;
}
else if (reaction.user && client.userID !== reaction.user.id) {
console.warn(`message.own_reactions contained reactions from a different user, this indicates a bug`);
}
});
}
try {
if (userExistingReaction) {
await toggleReaction(message.id, userExistingReaction.type, false);
}
else {
await toggleReaction(message.id, reactionType, true);
}
}
catch (error) {
console.log({ error });
}
};
};