Skip to content

Commit 7990ef9

Browse files
committed
perf: implement RAF based coalescing
1 parent d9f820a commit 7990ef9

File tree

4 files changed

+59
-12
lines changed

4 files changed

+59
-12
lines changed

package/src/components/MessageList/MessageList.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,10 +338,10 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
338338

339339
const maintainVisibleContentPosition = useMemo(
340340
() => ({
341-
autoscrollToTopThreshold: autoscrollToRecent ? 10 : undefined,
341+
autoscrollToTopThreshold: 64,
342342
minIndexForVisible,
343343
}),
344-
[autoscrollToRecent, minIndexForVisible],
344+
[minIndexForVisible],
345345
);
346346

347347
/**
@@ -1218,7 +1218,10 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
12181218
onViewableItemsChanged={stableOnViewableItemsChanged}
12191219
ref={refCallback}
12201220
renderItem={renderItem}
1221+
scrollEventThrottle={16}
12211222
showsVerticalScrollIndicator={false}
1223+
// @ts-expect-error react-native internal
1224+
strictMode={true}
12221225
style={flatListStyle}
12231226
testID='message-flat-list'
12241227
viewabilityConfig={flatListViewabilityConfig}

package/src/components/MessageList/hooks/useMessageList.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import { usePaginatedMessageListContext } from '../../../contexts/paginatedMessageListContext/PaginatedMessageListContext';
1212
import { useThreadContext } from '../../../contexts/threadContext/ThreadContext';
1313

14+
import { useRafCoalescedValue } from '../../../hooks';
1415
import { DateSeparators, getDateSeparators } from '../utils/getDateSeparators';
1516
import { getGroupStyles } from '../utils/getGroupStyles';
1617

@@ -110,14 +111,19 @@ export const useMessageList = (params: UseMessageListParams) => {
110111
return newMessageList;
111112
}, [client.userID, deletedMessagesVisibilityType, messageList]);
112113

113-
return {
114-
/** Date separators */
115-
dateSeparatorsRef,
116-
/** Message group styles */
117-
messageGroupStylesRef,
118-
/** Messages enriched with dates/readby/groups and also reversed in order */
119-
processedMessageList,
120-
/** Raw messages from the channel state */
121-
rawMessageList: messageList,
122-
};
114+
const data = useRafCoalescedValue(processedMessageList);
115+
116+
return useMemo(
117+
() => ({
118+
/** Date separators */
119+
dateSeparatorsRef,
120+
/** Message group styles */
121+
messageGroupStylesRef,
122+
/** Messages enriched with dates/readby/groups and also reversed in order */
123+
processedMessageList: data,
124+
/** Raw messages from the channel state */
125+
rawMessageList: messageList,
126+
}),
127+
[data, messageList],
128+
);
123129
};

package/src/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ export * from './useMessageReminder';
99
export * from './useQueryReminders';
1010
export * from './useClientNotifications';
1111
export * from './useInAppNotificationsState';
12+
export * from './useRafCoalescedValue';
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { useEffect, useRef, useState } from 'react';
2+
3+
/**
4+
* Coalesce a fast-changing value so React consumers only see updates:
5+
* - at most once per animation frame (RAF), and
6+
* - optionally no more often than `minIntervalMs`.
7+
*/
8+
export const useRafCoalescedValue = <S>(value: S): S => {
9+
const [emitted, setEmitted] = useState<S>(value);
10+
const pendingRef = useRef<S>(value);
11+
const rafIdRef = useRef<number | 0>(0 as const);
12+
13+
// If `value` changes, schedule a single RAF to publish the latest one.
14+
useEffect(() => {
15+
if (value === pendingRef.current) return;
16+
pendingRef.current = value;
17+
18+
if (rafIdRef.current) return; // already scheduled this frame
19+
20+
const run = () => {
21+
rafIdRef.current = 0;
22+
setEmitted(pendingRef.current);
23+
};
24+
25+
rafIdRef.current = requestAnimationFrame(run);
26+
27+
return () => {
28+
// cancel the frame if needed
29+
if (rafIdRef.current) {
30+
cancelAnimationFrame(rafIdRef.current);
31+
rafIdRef.current = 0;
32+
}
33+
};
34+
}, [value]);
35+
36+
return emitted;
37+
};

0 commit comments

Comments
 (0)