Skip to content

Commit d63d7cf

Browse files
Initial commit
1 parent 00af860 commit d63d7cf

4 files changed

Lines changed: 190 additions & 9 deletions

File tree

src/components/Reactions/MessageReactionsDetail.tsx

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import React, { useMemo, useState } from 'react';
22

33
import type { ReactionSummary, ReactionType } from './types';
44

5-
import { useFetchReactions } from './hooks/useFetchReactions';
65
import { Avatar as DefaultAvatar } from '../Avatar';
76
import type { MessageContextValue } from '../../context';
87
import {
@@ -14,8 +13,11 @@ import {
1413
import type { ReactionSort } from 'stream-chat';
1514
import { defaultReactionOptions } from './reactionOptions';
1615
import type { useProcessReactions } from './hooks/useProcessReactions';
16+
import { useReactionPaginator } from './hooks/useReactionPaginator';
1717
import { IconEmojiAdd } from '../Icons';
1818
import { ReactionSelector, type ReactionSelectorProps } from './ReactionSelector';
19+
import { LoadMorePaginator } from '../LoadMore';
20+
import { Button } from '../Button';
1921

2022
export type MessageReactionsDetailProps = Partial<
2123
Pick<MessageContextValue, 'handleFetchReactions' | 'reactionDetailsSort'>
@@ -52,7 +54,6 @@ interface MessageReactionsDetailInterface {
5254
}
5355

5456
export const MessageReactionsDetail: MessageReactionsDetailInterface = ({
55-
handleFetchReactions,
5657
handleReaction,
5758
onSelectedReactionTypeChange,
5859
own_reactions,
@@ -62,8 +63,8 @@ export const MessageReactionsDetail: MessageReactionsDetailInterface = ({
6263
selectedReactionType,
6364
totalReactionCount,
6465
}) => {
65-
const [extendedReactionListOpen, setExtendedReactionListOpen] = useState(false);
6666
const { client } = useChatContext();
67+
const [extendedReactionListOpen, setExtendedReactionListOpen] = useState(false);
6768
const {
6869
Avatar = DefaultAvatar,
6970
LoadingIndicator = MessageReactionsDetailLoadingIndicator,
@@ -82,13 +83,13 @@ export const MessageReactionsDetail: MessageReactionsDetailInterface = ({
8283
propReactionDetailsSort ?? contextReactionDetailsSort ?? defaultReactionDetailsSort;
8384

8485
const {
86+
hasNext,
8587
isLoading: areReactionsLoading,
88+
paginator,
8689
reactions: reactionDetails,
8790
refetch,
88-
} = useFetchReactions({
89-
handleFetchReactions,
91+
} = useReactionPaginator({
9092
reactionType: selectedReactionType,
91-
shouldFetch: true,
9293
sort: reactionDetailsSort,
9394
});
9495

@@ -191,8 +192,27 @@ export const MessageReactionsDetail: MessageReactionsDetailInterface = ({
191192
className='str-chat__message-reactions-detail__user-list'
192193
data-testid='all-reacting-users'
193194
>
194-
{areReactionsLoading && <LoadingIndicator />}
195-
{!areReactionsLoading && (
195+
<LoadMorePaginator
196+
hasNextPage={hasNext}
197+
isLoading={areReactionsLoading}
198+
LoadMoreButton={({ isLoading, onClick }) => {
199+
if (isLoading) return null;
200+
201+
return (
202+
<Button
203+
appearance='ghost'
204+
aria-label={t('Load more')}
205+
className='str-chat__button--load-more'
206+
onClick={onClick}
207+
size='xs'
208+
variant='secondary'
209+
>
210+
{t('Load more')}
211+
</Button>
212+
);
213+
}}
214+
loadNextPage={paginator.next}
215+
>
196216
<>
197217
{reactionDetails.map(({ type, user }) => {
198218
const belongsToCurrentUser = client.user?.id === user?.id;
@@ -257,7 +277,8 @@ export const MessageReactionsDetail: MessageReactionsDetailInterface = ({
257277
);
258278
})}
259279
</>
260-
)}
280+
</LoadMorePaginator>
281+
{areReactionsLoading && <LoadingIndicator />}
261282
</div>
262283
</div>
263284
</div>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import {
2+
BasePaginator,
3+
type PaginationQueryParams,
4+
type PaginatorOptions,
5+
type ReactionFilters,
6+
type ReactionResponse,
7+
type ReactionSort,
8+
type StreamChat,
9+
} from 'stream-chat';
10+
11+
export class ReactionPaginator extends BasePaginator<ReactionResponse> {
12+
private client: StreamChat;
13+
private messageId: string;
14+
private _filters: ReactionFilters;
15+
private _sort: ReactionSort;
16+
protected usesCursorPagination = true;
17+
18+
get filters(): ReactionFilters | undefined {
19+
return this._filters;
20+
}
21+
22+
get sort(): ReactionSort | undefined {
23+
return this._sort;
24+
}
25+
26+
set filters(filters: ReactionFilters) {
27+
this._filters = filters;
28+
this.invalidate();
29+
}
30+
31+
set sort(sort: ReactionSort) {
32+
this._sort = sort;
33+
this.invalidate();
34+
}
35+
36+
constructor({
37+
client,
38+
messageId,
39+
options,
40+
}: {
41+
client: StreamChat;
42+
messageId: string;
43+
options?: PaginatorOptions;
44+
}) {
45+
super(options);
46+
this.client = client;
47+
this.messageId = messageId;
48+
this._filters = {};
49+
this._sort = { created_at: -1 };
50+
}
51+
52+
async query(params: PaginationQueryParams) {
53+
const direction = params.direction;
54+
55+
const response = await this.client.queryReactions(
56+
this.messageId,
57+
this._filters,
58+
this._sort,
59+
{
60+
[direction]: direction === 'next' ? params.next : params.prev,
61+
limit: this.pageSize,
62+
},
63+
);
64+
65+
return {
66+
items: response.reactions,
67+
next: response.next,
68+
};
69+
}
70+
71+
public filterQueryResults(items: ReactionResponse[]) {
72+
return items;
73+
}
74+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { useCallback, useEffect, useRef } from 'react';
2+
import type { PaginatorState, ReactionResponse, ReactionSort } from 'stream-chat';
3+
4+
import { useChatContext, useMessageContext } from '../../../context';
5+
import { ReactionPaginator } from './ReactionPaginator';
6+
import { useStateStore } from '../../../store';
7+
import type { ReactionType } from '../types';
8+
9+
export interface FetchReactionsOptions {
10+
reactionType: ReactionType | null;
11+
sort?: ReactionSort;
12+
}
13+
14+
const STABLE_ARRAY: ReactionResponse[] = [];
15+
const reactionSelector = (currentState: PaginatorState<ReactionResponse>) => ({
16+
hasNext: currentState.hasNext,
17+
isLoading: currentState.isLoading,
18+
reactions: currentState.items ?? STABLE_ARRAY,
19+
});
20+
21+
// use null symbol instead of the actual null for the paginator key
22+
const nullSymbol = Symbol('null');
23+
24+
export function useReactionPaginator({ reactionType, sort }: FetchReactionsOptions) {
25+
const { client } = useChatContext();
26+
const { message } = useMessageContext('useReactionPaginator');
27+
28+
const paginatorByTypeRef = useRef<{
29+
[key: string | symbol]: ReactionPaginator | undefined;
30+
}>({});
31+
32+
const normalizedReactionType = reactionType === null ? nullSymbol : reactionType;
33+
34+
const getOrCreateInstance = () => {
35+
const existingInstance = paginatorByTypeRef.current[normalizedReactionType];
36+
37+
if (existingInstance) return existingInstance;
38+
39+
const instance = new ReactionPaginator({
40+
client,
41+
messageId: message.id,
42+
options: { pageSize: 25 },
43+
});
44+
if (reactionType) instance.filters = { type: reactionType };
45+
if (sort) instance.sort = sort;
46+
47+
return (paginatorByTypeRef.current[normalizedReactionType] = instance);
48+
};
49+
50+
const selectedPaginator = getOrCreateInstance();
51+
52+
const { hasNext, isLoading, reactions } = useStateStore(
53+
selectedPaginator.state,
54+
reactionSelector,
55+
);
56+
57+
const refetch = useCallback(() => {
58+
selectedPaginator.invalidate();
59+
selectedPaginator.next();
60+
}, [selectedPaginator]);
61+
62+
useEffect(() => {
63+
const data = selectedPaginator.state.getLatestValue().items;
64+
65+
if (data?.length) return;
66+
67+
selectedPaginator.next();
68+
}, [selectedPaginator]);
69+
70+
return {
71+
hasNext,
72+
isLoading,
73+
paginator: selectedPaginator,
74+
reactions,
75+
refetch,
76+
} as const;
77+
}

src/components/Reactions/styling/MessageReactionsDetail.scss

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,15 @@
103103
position: relative;
104104
padding-block-end: var(--str-chat__spacing-xxs);
105105
max-height: 180px;
106+
display: flex;
107+
flex-direction: column;
108+
109+
.str-chat__button.str-chat__button--load-more {
110+
align-self: center;
111+
flex-shrink: 0;
112+
margin-block-end: var(--str-chat__spacing-xs);
113+
margin-block-start: var(--str-chat__spacing-xxs);
114+
}
106115

107116
.str-chat__message-reactions-detail__skeleton-item {
108117
padding-block: var(--str-chat__spacing-xxs);

0 commit comments

Comments
 (0)