Skip to content

Commit 21eab51

Browse files
committed
perf: throttle certain channel preview updates to avoid stressing the state update queue
1 parent 36f28a8 commit 21eab51

File tree

1 file changed

+59
-29
lines changed

1 file changed

+59
-29
lines changed

package/src/components/ChannelPreview/hooks/useChannelPreviewData.ts

Lines changed: 59 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useMemo, useState } from 'react';
1+
import { type SetStateAction, useEffect, useMemo, useState } from 'react';
22

33
import throttle from 'lodash/throttle';
44
import type { Channel, ChannelState, Event, MessageResponse, StreamChat } from 'stream-chat';
@@ -8,16 +8,37 @@ import { useIsChannelMuted } from './useIsChannelMuted';
88
import { useLatestMessagePreview } from './useLatestMessagePreview';
99

1010
import { useChannelsContext } from '../../../contexts';
11+
import { useStableCallback } from '../../../hooks';
12+
13+
const setLastMessageThrottleTimeout = 500;
14+
const setLastMessageThrottleOptions = { leading: true, trailing: true };
15+
16+
const refreshUnreadCountThrottleTimeout = 400;
17+
const refreshUnreadCountThrottleOptions = setLastMessageThrottleOptions;
18+
19+
type LastMessageType = ReturnType<ChannelState['formatMessage']> | MessageResponse;
1120

1221
export const useChannelPreviewData = (
1322
channel: Channel,
1423
client: StreamChat,
1524
forceUpdateOverride?: number,
1625
) => {
1726
const [forceUpdate, setForceUpdate] = useState(0);
18-
const [lastMessage, setLastMessage] = useState<
19-
ReturnType<ChannelState['formatMessage']> | MessageResponse
20-
>(channel.state.messages[channel.state.messages.length - 1]);
27+
const [lastMessage, setLastMessageInner] = useState<LastMessageType>(
28+
() => channel.state.messages[channel.state.messages.length - 1],
29+
);
30+
const throttledSetLastMessage = useMemo(
31+
() =>
32+
throttle(
33+
(newLastMessage: SetStateAction<LastMessageType>) => setLastMessageInner(newLastMessage),
34+
setLastMessageThrottleTimeout,
35+
setLastMessageThrottleOptions,
36+
),
37+
[],
38+
);
39+
const setLastMessage = useStableCallback((newLastMessage: SetStateAction<LastMessageType>) =>
40+
throttledSetLastMessage(newLastMessage),
41+
);
2142
const [unread, setUnread] = useState(channel.countUnread());
2243
const { muted } = useIsChannelMuted(channel);
2344
const { forceUpdate: contextForceUpdate } = useChannelsContext();
@@ -26,29 +47,50 @@ export const useChannelPreviewData = (
2647
const channelLastMessage = channel.lastMessage();
2748
const channelLastMessageString = `${channelLastMessage?.id}${channelLastMessage?.updated_at}`;
2849

50+
const refreshUnreadCount = useMemo(
51+
() =>
52+
throttle(
53+
() => {
54+
if (muted) {
55+
setUnread(0);
56+
} else {
57+
setUnread(channel.countUnread());
58+
}
59+
},
60+
refreshUnreadCountThrottleTimeout,
61+
refreshUnreadCountThrottleOptions,
62+
),
63+
[channel, muted],
64+
);
65+
2966
useEffect(() => {
3067
const unsubscribe = channel.messageComposer.registerDraftEventSubscriptions();
3168
return () => unsubscribe();
3269
}, [channel.messageComposer]);
3370

3471
useEffect(() => {
3572
const { unsubscribe } = client.on('notification.mark_read', () => {
36-
setUnread(channel.countUnread());
73+
refreshUnreadCount();
3774
});
3875
return unsubscribe;
39-
}, [channel, client]);
76+
}, [client, refreshUnreadCount]);
4077

4178
useEffect(() => {
42-
if (
79+
setLastMessage((prevLastMessage) =>
4380
channelLastMessage &&
44-
(channelLastMessage.id !== lastMessage?.id ||
45-
channelLastMessage.updated_at !== lastMessage?.updated_at)
46-
) {
47-
setLastMessage(channelLastMessage);
48-
}
49-
const newUnreadCount = channel.countUnread();
50-
setUnread(newUnreadCount);
51-
}, [channel, channelLastMessage, channelLastMessageString, channelListForceUpdate, lastMessage]);
81+
(channelLastMessage.id !== prevLastMessage?.id ||
82+
channelLastMessage.updated_at !== prevLastMessage?.updated_at)
83+
? channelLastMessage
84+
: prevLastMessage,
85+
);
86+
refreshUnreadCount();
87+
}, [
88+
channelLastMessage,
89+
channelLastMessageString,
90+
channelListForceUpdate,
91+
setLastMessage,
92+
refreshUnreadCount,
93+
]);
5294

5395
/**
5496
* This effect listens for the `notification.mark_read` event and sets the unread count to 0
@@ -91,18 +133,6 @@ export const useChannelPreviewData = (
91133
return unsubscribe;
92134
}, [client, channel]);
93135

94-
const refreshUnreadCount = useMemo(
95-
() =>
96-
throttle(() => {
97-
if (muted) {
98-
setUnread(0);
99-
} else {
100-
setUnread(channel.countUnread());
101-
}
102-
}, 400),
103-
[channel, muted],
104-
);
105-
106136
/**
107137
* This effect listens for the `message.new`, `message.updated`, `message.deleted`, `message.undeleted`, and `channel.truncated` events
108138
*/
@@ -118,7 +148,7 @@ export const useChannelPreviewData = (
118148
const message = event.message;
119149
if (message && (!message.parent_id || message.show_in_channel)) {
120150
setLastMessage(message);
121-
setUnread(channel.countUnread());
151+
refreshUnreadCount();
122152
}
123153
};
124154

@@ -140,7 +170,7 @@ export const useChannelPreviewData = (
140170
];
141171

142172
return () => listeners.forEach((l) => l.unsubscribe());
143-
}, [channel, refreshUnreadCount, forceUpdate, channelListForceUpdate]);
173+
}, [channel, refreshUnreadCount, forceUpdate, channelListForceUpdate, setLastMessage]);
144174

145175
const latestMessagePreview = useLatestMessagePreview(channel, forceUpdate, lastMessage);
146176

0 commit comments

Comments
 (0)