Skip to content

Commit a1dadf3

Browse files
committed
fix: improve the entire channel list re-render for useUserPresence hook
1 parent 0cb1020 commit a1dadf3

File tree

6 files changed

+100
-19
lines changed

6 files changed

+100
-19
lines changed

package/src/components/ChannelList/ChannelList.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { ChannelListHeaderNetworkDownIndicator } from './ChannelListHeaderNetwor
1010
import { ChannelListLoadingIndicator } from './ChannelListLoadingIndicator';
1111
import { ChannelListMessenger, ChannelListMessengerProps } from './ChannelListMessenger';
1212
import { useChannelUpdated } from './hooks/listeners/useChannelUpdated';
13-
import { useUserPresence } from './hooks/listeners/useUserPresence';
1413
import { useCreateChannelsContext } from './hooks/useCreateChannelsContext';
1514
import { usePaginatedChannels } from './hooks/usePaginatedChannels';
1615
import { Skeleton as SkeletonDefault } from './Skeleton';
@@ -367,11 +366,6 @@ export const ChannelList = <
367366
setChannels: channelManager.setChannels,
368367
});
369368

370-
useUserPresence({
371-
setChannels: channelManager.setChannels,
372-
setForceUpdate,
373-
});
374-
375369
const channelIdsStr = channels?.reduce((acc, channel) => `${acc}${channel.cid}`, '');
376370

377371
useEffect(() => {

package/src/components/ChannelList/hooks/listeners/useUserPresence.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ type Parameters<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultSt
1212
setForceUpdate: React.Dispatch<React.SetStateAction<number>>;
1313
};
1414

15+
/**
16+
* Hook to update the channel members when the user presence changes
17+
* @deprecated this hook will be removed in favour of the useChannelPreviewDisplayPresence to improve performance
18+
*/
1519
export const useUserPresence = <
1620
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
1721
>({

package/src/components/ChannelList/hooks/useChannelMembershipState.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Channel, ChannelMemberResponse, EventTypes } from 'stream-chat';
22

3-
import { useSelectedChannelState } from './useSelectedChannelState';
3+
import { useSelectedChannelState } from '../../../hooks/useSelectedChannelState';
44

55
import { DefaultStreamChatGenerics } from '../../../types/types';
66

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,41 @@
1-
import type { Channel } from 'stream-chat';
1+
import type { Channel, EventTypes, StreamChat } from 'stream-chat';
22

33
import { useChatContext } from '../../../contexts/chatContext/ChatContext';
44

5+
import { useSyncClientEventsToChannel } from '../../../hooks/useSyncClientEvents';
56
import type { DefaultStreamChatGenerics } from '../../../types/types';
67

78
/**
8-
* Hook to set the display avatar presence for channel preview
9-
* @param {*} channel
9+
* Selector to get the display avatar presence for channel preview
10+
* @param channel
11+
* @param client
12+
* @returns boolean
1013
*
11-
* @returns {boolean} e.g., true
14+
* NOTE: If you want to listen to the value changes where you call the hook, the selector should return primitive values instead of object.
1215
*/
13-
export const useChannelPreviewDisplayPresence = <
14-
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
15-
>(
16+
const selector = <StreamChatGenerics extends DefaultStreamChatGenerics>(
1617
channel: Channel<StreamChatGenerics>,
18+
client: StreamChat<StreamChatGenerics>,
1719
) => {
18-
const { client } = useChatContext<StreamChatGenerics>();
1920
const members = channel.state.members;
2021
const membersCount = Object.keys(members).length;
21-
22-
if (membersCount !== 2) return false;
23-
2422
const otherMember = Object.values(members).find((member) => member.user?.id !== client.userID);
2523

24+
if (membersCount !== 2) return false;
2625
return otherMember?.user?.online ?? false;
2726
};
27+
28+
const keys: EventTypes[] = ['user.presence.changed', 'user.updated'];
29+
30+
/**
31+
* Hook to set the display avatar presence for channel preview
32+
* @param {*} channel
33+
*
34+
* @returns {boolean} e.g., true
35+
*/
36+
export function useChannelPreviewDisplayPresence<
37+
StreamChatGenerics extends DefaultStreamChatGenerics,
38+
>(channel: Channel<StreamChatGenerics>) {
39+
const { client } = useChatContext<StreamChatGenerics>();
40+
return useSyncClientEventsToChannel({ channel, client, selector, stateChangeEventKeys: keys });
41+
}

package/src/components/ChannelList/hooks/useSelectedChannelState.ts renamed to package/src/hooks/useSelectedChannelState.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useCallback } from 'react';
33
import type { Channel, EventTypes } from 'stream-chat';
44
import { useSyncExternalStore } from 'use-sync-external-store/shim';
55

6-
import { DefaultStreamChatGenerics } from '../../../types/types';
6+
import { DefaultStreamChatGenerics } from '../types/types';
77

88
const noop = () => {};
99

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { useCallback } from 'react';
2+
3+
import type { Channel, EventTypes, StreamChat } from 'stream-chat';
4+
import { useSyncExternalStore } from 'use-sync-external-store/shim';
5+
6+
import { DefaultStreamChatGenerics } from '../types/types';
7+
8+
const noop = () => {};
9+
10+
export function useSyncClientEventsToChannel<
11+
StreamChatGenerics extends DefaultStreamChatGenerics,
12+
O,
13+
>(_: {
14+
channel: Channel<StreamChatGenerics>;
15+
client: StreamChat<StreamChatGenerics>;
16+
selector: (channel: Channel<StreamChatGenerics>, client: StreamChat<StreamChatGenerics>) => O;
17+
stateChangeEventKeys?: EventTypes[];
18+
}): O;
19+
export function useSyncClientEventsToChannel<
20+
StreamChatGenerics extends DefaultStreamChatGenerics,
21+
O,
22+
>(_: {
23+
selector: (channel: Channel<StreamChatGenerics>, client: StreamChat<StreamChatGenerics>) => O;
24+
channel?: Channel<StreamChatGenerics> | undefined;
25+
client?: StreamChat<StreamChatGenerics> | undefined;
26+
stateChangeEventKeys?: EventTypes[];
27+
}): O | undefined;
28+
export function useSyncClientEventsToChannel<
29+
StreamChatGenerics extends DefaultStreamChatGenerics,
30+
O,
31+
>({
32+
channel,
33+
client,
34+
selector,
35+
stateChangeEventKeys = ['all'],
36+
}: {
37+
selector: (channel: Channel<StreamChatGenerics>, client: StreamChat<StreamChatGenerics>) => O;
38+
channel?: Channel<StreamChatGenerics> | undefined;
39+
client?: StreamChat<StreamChatGenerics>;
40+
stateChangeEventKeys?: EventTypes[];
41+
}): O | undefined {
42+
const subscribe = useCallback(
43+
(onStoreChange: (value: O) => void) => {
44+
if (!client || !channel) {
45+
return noop;
46+
}
47+
48+
const subscriptions = stateChangeEventKeys.map((et) =>
49+
client.on(et, () => {
50+
onStoreChange(selector(channel, client));
51+
}),
52+
);
53+
54+
return () => subscriptions.forEach((subscription) => subscription.unsubscribe());
55+
},
56+
[channel, client, selector, stateChangeEventKeys],
57+
);
58+
59+
const getSnapshot = useCallback(() => {
60+
if (!client || !channel) {
61+
return undefined;
62+
}
63+
64+
const originalSnapshot = selector(channel, client);
65+
return originalSnapshot;
66+
}, [channel, client, selector]);
67+
68+
return useSyncExternalStore(subscribe, getSnapshot);
69+
}

0 commit comments

Comments
 (0)