Skip to content

Commit 913875d

Browse files
committed
fix(chat-hooks): respect the order of group channel collection and query.
1 parent 301801c commit 913875d

File tree

10 files changed

+395
-304
lines changed

10 files changed

+395
-304
lines changed

packages/uikit-chat-hooks/src/channel/useGroupChannelList.ts renamed to packages/uikit-chat-hooks/src/channel/useGroupChannelList/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { UseGroupChannelList } from '../types';
1+
import type { UseGroupChannelList } from '../../types';
22
import { useGroupChannelListWithCollection } from './useGroupChannelListWithCollection';
33
import { useGroupChannelListWithQuery } from './useGroupChannelListWithQuery';
44

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import { useReducer } from 'react';
2+
3+
import type { SendbirdChannel } from '@sendbird/uikit-utils';
4+
import { SendbirdGroupChannel, getGroupChannels } from '@sendbird/uikit-utils';
5+
6+
type Order = 'latest_last_message' | 'chronological' | 'channel_name_alphabetical' | 'metadata_value_alphabetical';
7+
8+
type Action =
9+
| {
10+
type: 'update_loading' | 'update_refreshing';
11+
value: { status: boolean };
12+
}
13+
| {
14+
type: 'update_channels';
15+
value: { channels: SendbirdChannel[] };
16+
}
17+
| {
18+
type: 'delete_channels';
19+
value: { channelUrls: string[] };
20+
}
21+
| {
22+
type: 'set_channels';
23+
value: { channels: SendbirdChannel[]; clearPrev: boolean };
24+
}
25+
| {
26+
type: 'update_order';
27+
value: { order?: Order };
28+
};
29+
30+
type State = {
31+
loading: boolean;
32+
refreshing: boolean;
33+
groupChannels: SendbirdGroupChannel[];
34+
order?: Order;
35+
};
36+
37+
const defaultReducer = ({ ...draft }: State, action: Action) => {
38+
const compareByOrder = createCompareByOrder(draft.order);
39+
40+
switch (action.type) {
41+
case 'update_refreshing':
42+
case 'update_loading': {
43+
const key = action.type === 'update_loading' ? 'loading' : 'refreshing';
44+
draft[key] = action.value.status;
45+
break;
46+
}
47+
case 'update_channels': {
48+
getGroupChannels(action.value.channels).forEach((freshChannel) => {
49+
const idx = draft.groupChannels.findIndex((staleChannel) => staleChannel.url === freshChannel.url);
50+
if (idx > -1) draft.groupChannels[idx] = freshChannel;
51+
});
52+
53+
compareByOrder && (draft.groupChannels = draft.groupChannels.sort(compareByOrder));
54+
break;
55+
}
56+
case 'delete_channels': {
57+
action.value.channelUrls.forEach((url) => {
58+
const idx = draft.groupChannels.findIndex((c) => c.url === url);
59+
if (idx > -1) draft.groupChannels.splice(idx, 1);
60+
});
61+
62+
compareByOrder && (draft.groupChannels = draft.groupChannels.sort(compareByOrder));
63+
break;
64+
}
65+
case 'set_channels': {
66+
if (action.value.clearPrev) {
67+
draft.groupChannels = getGroupChannels(action.value.channels);
68+
} else {
69+
draft.groupChannels = [...draft.groupChannels, ...getGroupChannels(action.value.channels)];
70+
}
71+
72+
compareByOrder && (draft.groupChannels = draft.groupChannels.sort(compareByOrder));
73+
break;
74+
}
75+
case 'update_order': {
76+
draft.order = action.value.order;
77+
break;
78+
}
79+
}
80+
return draft;
81+
};
82+
83+
export const useGroupChannelListReducer = (order?: Order) => {
84+
const [{ loading, refreshing, groupChannels }, dispatch] = useReducer(defaultReducer, {
85+
loading: true,
86+
refreshing: false,
87+
groupChannels: [],
88+
order,
89+
});
90+
91+
const updateChannels = (channels: SendbirdChannel[]) => {
92+
dispatch({ type: 'update_channels', value: { channels } });
93+
};
94+
const deleteChannels = (channelUrls: string[]) => {
95+
dispatch({ type: 'delete_channels', value: { channelUrls } });
96+
};
97+
const setChannels = (channels: SendbirdChannel[], clearPrev: boolean) => {
98+
dispatch({ type: 'set_channels', value: { channels, clearPrev } });
99+
};
100+
const updateLoading = (status: boolean) => {
101+
dispatch({ type: 'update_loading', value: { status } });
102+
};
103+
const updateRefreshing = (status: boolean) => {
104+
dispatch({ type: 'update_refreshing', value: { status } });
105+
};
106+
const updateOrder = (order?: Order) => {
107+
dispatch({ type: 'update_order', value: { order } });
108+
};
109+
110+
return {
111+
updateLoading,
112+
updateRefreshing,
113+
updateChannels,
114+
deleteChannels,
115+
setChannels,
116+
117+
updateOrder,
118+
119+
loading,
120+
refreshing,
121+
groupChannels,
122+
};
123+
};
124+
125+
const createCompareByOrder = (order?: Order) => {
126+
if (!order) return undefined;
127+
128+
return (channel1: SendbirdGroupChannel, channel2: SendbirdGroupChannel): number => {
129+
switch (order) {
130+
case 'latest_last_message': {
131+
if (channel1.lastMessage && channel2.lastMessage) {
132+
return channel2.lastMessage.createdAt - channel1.lastMessage.createdAt;
133+
} else if (channel1.lastMessage) {
134+
return -1;
135+
} else if (channel2.lastMessage) {
136+
return 1;
137+
} else {
138+
return channel2.createdAt - channel1.createdAt;
139+
}
140+
}
141+
142+
case 'chronological': {
143+
return channel2.createdAt - channel1.createdAt;
144+
}
145+
146+
case 'channel_name_alphabetical': {
147+
return channel1.name.localeCompare(channel2.name);
148+
}
149+
default: {
150+
return 0;
151+
}
152+
}
153+
};
154+
};
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { useEffect, useRef } from 'react';
2+
3+
import type { SendbirdChatSDK, SendbirdGroupChannelCollection } from '@sendbird/uikit-utils';
4+
import { useAsyncEffect, useFreshCallback, useUniqId } from '@sendbird/uikit-utils';
5+
6+
import { useAppFeatures } from '../../common/useAppFeatures';
7+
import { useChannelHandler } from '../../handler/useChannelHandler';
8+
import type { UseGroupChannelList, UseGroupChannelListOptions } from '../../types';
9+
import { useGroupChannelListReducer } from './reducer';
10+
11+
const HOOK_NAME = 'useGroupChannelListWithCollection';
12+
13+
const createGroupChannelListCollection = (
14+
sdk: SendbirdChatSDK,
15+
collectionCreator: UseGroupChannelListOptions['collectionCreator'],
16+
) => {
17+
const passedCollection = collectionCreator?.();
18+
if (passedCollection) return passedCollection;
19+
20+
const defaultOptions = {
21+
includeEmpty: false,
22+
limit: 5,
23+
order: sdk.GroupChannelCollection.GroupChannelOrder.LATEST_LAST_MESSAGE,
24+
};
25+
const collectionBuilder = sdk.GroupChannel.createGroupChannelCollection();
26+
const groupChannelFilter = new sdk.GroupChannelFilter();
27+
groupChannelFilter.includeEmpty = defaultOptions.includeEmpty;
28+
29+
return collectionBuilder
30+
.setFilter(groupChannelFilter)
31+
.setLimit(defaultOptions.limit)
32+
.setOrder(defaultOptions.order)
33+
.build();
34+
};
35+
36+
export const useGroupChannelListWithCollection: UseGroupChannelList = (sdk, userId, options) => {
37+
const id = useUniqId(HOOK_NAME);
38+
const { deliveryReceiptEnabled } = useAppFeatures(sdk);
39+
40+
const collectionRef = useRef<SendbirdGroupChannelCollection>();
41+
42+
const { loading, groupChannels, refreshing, setChannels, deleteChannels, updateRefreshing, updateLoading } =
43+
useGroupChannelListReducer();
44+
45+
const updateChannelsAndMarkAsDelivered = (markAsDelivered: boolean) => {
46+
const channels = collectionRef.current?.channelList ?? [];
47+
setChannels(channels, true);
48+
if (markAsDelivered && deliveryReceiptEnabled) channels.forEach((channel) => sdk.markAsDelivered(channel.url));
49+
};
50+
51+
const init = useFreshCallback(async (uid?: string) => {
52+
if (collectionRef.current) collectionRef.current?.dispose();
53+
54+
if (uid) {
55+
collectionRef.current = createGroupChannelListCollection(sdk, options?.collectionCreator);
56+
57+
if (collectionRef.current?.hasMore) {
58+
await collectionRef.current?.loadMore();
59+
updateChannelsAndMarkAsDelivered(true);
60+
}
61+
62+
collectionRef.current?.setGroupChannelCollectionHandler({
63+
onChannelsAdded() {
64+
updateChannelsAndMarkAsDelivered(true);
65+
},
66+
onChannelsUpdated() {
67+
updateChannelsAndMarkAsDelivered(true);
68+
},
69+
onChannelsDeleted() {
70+
updateChannelsAndMarkAsDelivered(false);
71+
},
72+
});
73+
}
74+
});
75+
76+
useEffect(() => {
77+
return () => {
78+
if (collectionRef.current) collectionRef.current?.dispose();
79+
};
80+
}, []);
81+
82+
useAsyncEffect(async () => {
83+
updateLoading(true);
84+
await init(userId);
85+
updateLoading(false);
86+
}, [init, userId]);
87+
88+
useChannelHandler(sdk, `${HOOK_NAME}_${id}`, {
89+
onUserBanned: (channel, user) => {
90+
const isMe = user.userId === userId;
91+
if (isMe) deleteChannels([channel.url]);
92+
else updateChannelsAndMarkAsDelivered(false);
93+
},
94+
});
95+
96+
const refresh = useFreshCallback(async () => {
97+
updateRefreshing(true);
98+
await init(userId);
99+
updateRefreshing(false);
100+
});
101+
102+
const next = useFreshCallback(async () => {
103+
if (collectionRef.current?.hasMore) {
104+
await collectionRef.current?.loadMore();
105+
updateChannelsAndMarkAsDelivered(true);
106+
}
107+
});
108+
109+
return {
110+
loading,
111+
groupChannels,
112+
refresh,
113+
refreshing,
114+
next,
115+
};
116+
};
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { useRef } from 'react';
2+
import type Sendbird from 'sendbird';
3+
4+
import type { SendbirdChannel, SendbirdChatSDK } from '@sendbird/uikit-utils';
5+
import { useAsyncEffect, useFreshCallback } from '@sendbird/uikit-utils';
6+
7+
import { useAppFeatures } from '../../common/useAppFeatures';
8+
import { useChannelHandler } from '../../handler/useChannelHandler';
9+
import type { UseGroupChannelList, UseGroupChannelListOptions } from '../../types';
10+
import { useGroupChannelListReducer } from './reducer';
11+
12+
const HOOK_NAME = 'useGroupChannelListWithQuery';
13+
14+
const createGroupChannelListQuery = (
15+
sdk: SendbirdChatSDK,
16+
queryCreator: UseGroupChannelListOptions['queryCreator'],
17+
) => {
18+
const passedQuery = queryCreator?.();
19+
if (passedQuery) return passedQuery;
20+
21+
const defaultOptions = {
22+
includeEmpty: false,
23+
limit: 20,
24+
order: 'latest_last_message',
25+
} as const;
26+
const defaultQuery = sdk.GroupChannel.createMyGroupChannelListQuery();
27+
defaultQuery.limit = defaultOptions.limit;
28+
defaultQuery.order = defaultOptions.order;
29+
defaultQuery.includeEmpty = defaultOptions.includeEmpty;
30+
return defaultQuery;
31+
};
32+
33+
export const useGroupChannelListWithQuery: UseGroupChannelList = (sdk, userId, options) => {
34+
const { deliveryReceiptEnabled } = useAppFeatures(sdk);
35+
const queryRef = useRef<Sendbird.GroupChannelListQuery>();
36+
37+
const {
38+
loading,
39+
groupChannels,
40+
refreshing,
41+
updateChannels,
42+
setChannels,
43+
deleteChannels,
44+
updateRefreshing,
45+
updateLoading,
46+
updateOrder,
47+
} = useGroupChannelListReducer();
48+
49+
const updateChannelsAndMarkAsDelivered = (channels: SendbirdChannel[]) => {
50+
updateChannels(channels);
51+
if (deliveryReceiptEnabled) channels.forEach((channel) => sdk.markAsDelivered(channel.url));
52+
};
53+
54+
const init = useFreshCallback(async (uid?: string) => {
55+
if (uid) {
56+
queryRef.current = createGroupChannelListQuery(sdk, options?.queryCreator);
57+
updateOrder(queryRef.current?.order);
58+
59+
if (queryRef.current?.hasNext) {
60+
const channels = await queryRef.current.next();
61+
62+
setChannels(channels, true);
63+
if (deliveryReceiptEnabled) channels.forEach((channel) => sdk.markAsDelivered(channel.url));
64+
}
65+
}
66+
});
67+
68+
useAsyncEffect(async () => {
69+
updateLoading(true);
70+
await init(userId);
71+
updateLoading(false);
72+
}, [init, userId]);
73+
74+
useChannelHandler(sdk, HOOK_NAME, {
75+
onChannelChanged: (channel) => updateChannelsAndMarkAsDelivered([channel]),
76+
onChannelFrozen: (channel) => updateChannels([channel]),
77+
onChannelUnfrozen: (channel) => updateChannels([channel]),
78+
onChannelMemberCountChanged: (channels) => updateChannels(channels),
79+
onChannelDeleted: (url) => deleteChannels([url]),
80+
onUserJoined: (channel) => updateChannelsAndMarkAsDelivered([channel]),
81+
onUserLeft: (channel, user) => {
82+
const isMe = user.userId === userId;
83+
if (isMe) deleteChannels([channel.url]);
84+
else updateChannelsAndMarkAsDelivered([channel]);
85+
},
86+
onUserBanned(channel, user) {
87+
const isMe = user.userId === userId;
88+
if (isMe) deleteChannels([channel.url]);
89+
else updateChannelsAndMarkAsDelivered([channel]);
90+
},
91+
});
92+
93+
const refresh = useFreshCallback(async () => {
94+
updateRefreshing(true);
95+
await init(userId);
96+
updateRefreshing(false);
97+
});
98+
99+
const next = useFreshCallback(async () => {
100+
if (queryRef.current?.hasNext) {
101+
const channels = await queryRef.current.next();
102+
setChannels(channels, false);
103+
if (deliveryReceiptEnabled) channels.forEach((channel) => sdk.markAsDelivered(channel.url));
104+
}
105+
});
106+
107+
return {
108+
loading,
109+
groupChannels,
110+
refresh,
111+
refreshing,
112+
next,
113+
};
114+
};

0 commit comments

Comments
 (0)