stream-chat-react-native-core
Version:
The official React Native and Expo components for Stream Chat, a service for building chat applications
228 lines (198 loc) • 5.84 kB
text/typescript
import { useCallback, useEffect, useMemo, useState } from 'react';
import { LocalMessage, ReactionResponse, ReactionSort } from 'stream-chat';
import { useChatContext } from '../../../contexts/chatContext/ChatContext';
import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext';
import { useNotificationApi } from '../../Notifications';
export type UseFetchReactionParams = {
limit?: number;
message?: LocalMessage;
reactionType?: string;
sort?: ReactionSort;
};
const isSameReaction = (left: ReactionResponse, right: ReactionResponse) =>
left.user_id === right.user_id && left.type === right.type;
export const upsertReactionInList = ({
prevReactions,
reaction,
reactionType,
}: {
prevReactions: ReactionResponse[];
reaction: ReactionResponse;
reactionType?: string;
}) => {
if (!reactionType) {
return [
reaction,
...prevReactions.filter((currentReaction) => !isSameReaction(currentReaction, reaction)),
];
}
if (reaction.type !== reactionType) {
return prevReactions;
}
return [
reaction,
...prevReactions.filter((currentReaction) => currentReaction.user_id !== reaction.user_id),
];
};
export const reconcileUpdatedReactionInList = ({
prevReactions,
reaction,
reactionType,
}: {
prevReactions: ReactionResponse[];
reaction: ReactionResponse;
reactionType?: string;
}) => {
if (!reactionType) {
return [
reaction,
...prevReactions.filter((currentReaction) => !isSameReaction(currentReaction, reaction)),
];
}
if (reaction.type !== reactionType) {
return prevReactions.filter((currentReaction) => currentReaction.user_id !== reaction.user_id);
}
return [
reaction,
...prevReactions.filter((currentReaction) => currentReaction.user_id !== reaction.user_id),
];
};
export const removeReactionFromList = ({
prevReactions,
reaction,
reactionType,
}: {
prevReactions: ReactionResponse[];
reaction: ReactionResponse;
reactionType?: string;
}) => {
if (!reactionType) {
return prevReactions.filter((currentReaction) => !isSameReaction(currentReaction, reaction));
}
if (reaction.type !== reactionType) {
return prevReactions;
}
return prevReactions.filter((currentReaction) => currentReaction.user_id !== reaction.user_id);
};
export const useFetchReactions = ({
limit = 25,
message,
reactionType,
sort,
}: UseFetchReactionParams) => {
const [reactions, setReactions] = useState<ReactionResponse[]>([]);
const [loading, setLoading] = useState(true);
const [next, setNext] = useState<string | undefined>(undefined);
const messageId = message?.id;
const { client } = useChatContext();
const { t } = useTranslationContext();
const { addNotification } = useNotificationApi();
const sortString = useMemo(() => JSON.stringify(sort), [sort]);
const fetchReactions = useCallback(
async (next: string | undefined) => {
if (!messageId) {
return;
}
try {
const response = await client.queryReactions(
messageId,
reactionType ? { type: reactionType } : {},
sort,
{ limit, next },
);
if (response) {
setNext(response.next);
setReactions((prevReactions) =>
next ? [...prevReactions, ...response.reactions] : response.reactions,
);
setLoading(false);
}
} catch (error) {
addNotification({
message: t('Error fetching reactions'),
options: {
...(error instanceof Error ? { originalError: error } : {}),
severity: 'error',
type: 'api:message:reactions:fetch:failed',
},
origin: {
...(message ? { context: { message } } : {}),
emitter: 'Reactions',
},
});
}
},
[addNotification, client, limit, message, messageId, reactionType, sort, t],
);
const loadNextPage = useCallback(async () => {
if (next) {
await fetchReactions(next);
}
}, [fetchReactions, next]);
useEffect(() => {
setReactions([]);
setNext(undefined);
fetchReactions(undefined);
}, [fetchReactions, messageId, reactionType, sortString]);
useEffect(() => {
const listeners: ReturnType<typeof client.on>[] = [];
listeners.push(
client.on('offline_reactions.queried', (event) => {
const { offlineReactions } = event;
if (offlineReactions) {
setReactions(offlineReactions);
setLoading(false);
setNext(undefined);
}
}),
);
listeners.push(
client.on('reaction.new', (event) => {
const { reaction } = event;
if (reaction) {
setReactions((prevReactions) =>
upsertReactionInList({
prevReactions,
reaction,
reactionType,
}),
);
}
}),
);
listeners.push(
client.on('reaction.updated', (event) => {
const { reaction } = event;
if (reaction) {
setReactions((prevReactions) =>
reconcileUpdatedReactionInList({
prevReactions,
reaction,
reactionType,
}),
);
}
}),
);
listeners.push(
client.on('reaction.deleted', (event) => {
const { reaction } = event;
if (reaction) {
setReactions((prevReactions) =>
removeReactionFromList({
prevReactions,
reaction,
reactionType,
}),
);
}
}),
);
return () => {
listeners.forEach((listener) => {
listener?.unsubscribe();
});
};
}, [client, reactionType]);
return { loading, loadNextPage, reactions };
};