stream-chat-react-native-core 8.5.0 → 8.5.1-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/commonjs/components/Message/MessageSimple/utils/renderText.js +1 -1
- package/lib/commonjs/components/Message/MessageSimple/utils/renderText.js.map +1 -1
- package/lib/commonjs/components/MessageList/MessageList.js +13 -4
- package/lib/commonjs/components/MessageList/MessageList.js.map +1 -1
- package/lib/commonjs/components/MessageList/hooks/useMessageList.js +12 -7
- package/lib/commonjs/components/MessageList/hooks/useMessageList.js.map +1 -1
- package/lib/commonjs/hooks/index.js +11 -0
- package/lib/commonjs/hooks/index.js.map +1 -1
- package/lib/commonjs/hooks/useRAFCoalescedValue.js +35 -0
- package/lib/commonjs/hooks/useRAFCoalescedValue.js.map +1 -0
- package/lib/commonjs/version.json +1 -1
- package/lib/module/components/Message/MessageSimple/utils/renderText.js +1 -1
- package/lib/module/components/Message/MessageSimple/utils/renderText.js.map +1 -1
- package/lib/module/components/MessageList/MessageList.js +13 -4
- package/lib/module/components/MessageList/MessageList.js.map +1 -1
- package/lib/module/components/MessageList/hooks/useMessageList.js +12 -7
- package/lib/module/components/MessageList/hooks/useMessageList.js.map +1 -1
- package/lib/module/hooks/index.js +11 -0
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/hooks/useRAFCoalescedValue.js +35 -0
- package/lib/module/hooks/useRAFCoalescedValue.js.map +1 -0
- package/lib/module/version.json +1 -1
- package/lib/typescript/components/MessageList/MessageList.d.ts +9 -0
- package/lib/typescript/components/MessageList/MessageList.d.ts.map +1 -1
- package/lib/typescript/components/MessageList/hooks/useMessageList.d.ts +1 -0
- package/lib/typescript/components/MessageList/hooks/useMessageList.d.ts.map +1 -1
- package/lib/typescript/hooks/index.d.ts +1 -0
- package/lib/typescript/hooks/index.d.ts.map +1 -1
- package/lib/typescript/hooks/useRAFCoalescedValue.d.ts +43 -0
- package/lib/typescript/hooks/useRAFCoalescedValue.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/Message/MessageSimple/utils/renderText.tsx +1 -1
- package/src/components/MessageList/MessageList.tsx +27 -5
- package/src/components/MessageList/hooks/useMessageList.ts +18 -11
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useRAFCoalescedValue.ts +76 -0
- package/src/version.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A utility hook that coalesces a fast changing value to the display’s frame rate.
|
|
3
|
+
* It accepts any “noisy” input (arrays, objects, numbers, etc.) and exposes a value
|
|
4
|
+
* that React consumers will see at most once per animation frame (via
|
|
5
|
+
* `requestAnimationFrame`). This is useful when upstream sources (selectors, sockets,
|
|
6
|
+
* DB listeners) can fire multiple times within a single paint and you want to avoid
|
|
7
|
+
* extra renders and layout churn.
|
|
8
|
+
*
|
|
9
|
+
* How it works:
|
|
10
|
+
* - Keeps track of the latest incoming value
|
|
11
|
+
* - Ensures there is **at most one** pending RAF at a time
|
|
12
|
+
* - When the RAF fires, commits the **latest** value to state (`emitted`)
|
|
13
|
+
* - If additional changes arrive before the RAF runs, they are merged (the last write
|
|
14
|
+
* operation wins) and no new RAF is scheduled
|
|
15
|
+
*
|
|
16
|
+
* With this hook you can:
|
|
17
|
+
* - Feed a `FlatList`/`SectionList` from fast changing sources without spamming re-renders
|
|
18
|
+
* - Align React updates to the paint cadence (one publish per frame)
|
|
19
|
+
* - Help preserve item anchoring logic (e.g., MVCP) by reducing in-frame updates
|
|
20
|
+
*
|
|
21
|
+
* **Caveats:**
|
|
22
|
+
* - This hook intentionally skips intermediate states that occur within the same
|
|
23
|
+
* frame. If you must observe every transition (e.g., for analytics/reducers), do that
|
|
24
|
+
* upstream; this hook is for visual coalescing
|
|
25
|
+
* - Equality checks are simple referential equalities. If your producer recreates arrays
|
|
26
|
+
* or objects each time, you’ll still publish once per frame. To avoid even those
|
|
27
|
+
* emissions, stabilize upstream
|
|
28
|
+
* - This is not a silver bullet for throttle/debounce; it uses the screen’s refresh cycle;
|
|
29
|
+
* If you need “no more than once per X ms”, layer that upstream
|
|
30
|
+
*
|
|
31
|
+
* Usage tips:
|
|
32
|
+
* - Prefer passing already-memoized values when possible (e.g., stable arrays by ID).
|
|
33
|
+
* - Pair with a stable `keyExtractor` in lists so coalesced updates map cleanly to rows.
|
|
34
|
+
* - Do not cancel/reschedule on prop changes; cancellation is handled on unmount only.
|
|
35
|
+
*
|
|
36
|
+
* @param value The upstream value that may change multiple times within a single frame.
|
|
37
|
+
* @param isEnabled Determines whether the hook should be run or not (useful for cases where
|
|
38
|
+
* we want to conditionally use RAF when certain feature feature flags are enabled). If `false`,
|
|
39
|
+
* it will simply pass the data through (maintaining the reference as well).
|
|
40
|
+
* @returns A value that updates **at most once per frame** with the latest input.
|
|
41
|
+
*/
|
|
42
|
+
export declare const useRAFCoalescedValue: <S>(value: S, isEnabled: boolean | undefined) => S;
|
|
43
|
+
//# sourceMappingURL=useRAFCoalescedValue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useRAFCoalescedValue.d.ts","sourceRoot":"","sources":["../../../src/hooks/useRAFCoalescedValue.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,eAAO,MAAM,oBAAoB,GAAI,CAAC,EAAE,OAAO,CAAC,EAAE,WAAW,OAAO,GAAG,SAAS,KAAG,CAgClF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stream-chat-react-native-core",
|
|
3
3
|
"description": "The official React Native and Expo components for Stream Chat, a service for building chat applications",
|
|
4
|
-
"version": "8.5.
|
|
4
|
+
"version": "8.5.1-beta.2",
|
|
5
5
|
"author": {
|
|
6
6
|
"company": "Stream.io Inc",
|
|
7
7
|
"name": "Stream.io Inc"
|
|
@@ -525,7 +525,7 @@ const Bullet = ({ index, style }: BulletProps) => (
|
|
|
525
525
|
);
|
|
526
526
|
|
|
527
527
|
const ListRow = ({ children, style }: PropsWithChildren<ViewProps>) => (
|
|
528
|
-
<
|
|
528
|
+
<View style={style}>{children}</View>
|
|
529
529
|
);
|
|
530
530
|
|
|
531
531
|
const ListItem = ({ children, style }: PropsWithChildren<TextProps>) => (
|
|
@@ -222,6 +222,15 @@ type MessageListPropsWithContext = Pick<
|
|
|
222
222
|
* ```
|
|
223
223
|
*/
|
|
224
224
|
setFlatListRef?: (ref: FlatListType<LocalMessage> | null) => void;
|
|
225
|
+
/**
|
|
226
|
+
* If true, the message list will be used in a live-streaming scenario.
|
|
227
|
+
* This flag is used to make sure that the auto scroll behaves well, if multiple messages are received.
|
|
228
|
+
*
|
|
229
|
+
* This flag is experimental and is subject to change. Please test thoroughly before using it.
|
|
230
|
+
*
|
|
231
|
+
* @experimental
|
|
232
|
+
*/
|
|
233
|
+
isLiveStreaming?: boolean;
|
|
225
234
|
};
|
|
226
235
|
|
|
227
236
|
/**
|
|
@@ -256,6 +265,7 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
|
|
|
256
265
|
InlineUnreadIndicator,
|
|
257
266
|
inverted = true,
|
|
258
267
|
isListActive = false,
|
|
268
|
+
isLiveStreaming = false,
|
|
259
269
|
legacyImageViewerSwipeBehaviour,
|
|
260
270
|
loadChannelAroundMessage,
|
|
261
271
|
loading,
|
|
@@ -313,6 +323,7 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
|
|
|
313
323
|
*/
|
|
314
324
|
const { dateSeparatorsRef, messageGroupStylesRef, processedMessageList, rawMessageList } =
|
|
315
325
|
useMessageList({
|
|
326
|
+
isLiveStreaming,
|
|
316
327
|
noGroupByUser,
|
|
317
328
|
threadList,
|
|
318
329
|
});
|
|
@@ -336,12 +347,17 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
|
|
|
336
347
|
|
|
337
348
|
const minIndexForVisible = Math.min(1, processedMessageList.length);
|
|
338
349
|
|
|
350
|
+
const autoscrollToTopThreshold = useMemo(
|
|
351
|
+
() => (isLiveStreaming ? 64 : autoscrollToRecent ? 10 : undefined),
|
|
352
|
+
[autoscrollToRecent, isLiveStreaming],
|
|
353
|
+
);
|
|
354
|
+
|
|
339
355
|
const maintainVisibleContentPosition = useMemo(
|
|
340
356
|
() => ({
|
|
341
|
-
autoscrollToTopThreshold
|
|
357
|
+
autoscrollToTopThreshold,
|
|
342
358
|
minIndexForVisible,
|
|
343
359
|
}),
|
|
344
|
-
[
|
|
360
|
+
[autoscrollToTopThreshold, minIndexForVisible],
|
|
345
361
|
);
|
|
346
362
|
|
|
347
363
|
/**
|
|
@@ -652,7 +668,11 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
|
|
|
652
668
|
latestNonCurrentMessageBeforeUpdate?.id === latestCurrentMessageAfterUpdate.id;
|
|
653
669
|
// if didMergeMessageSetsWithNoUpdates=false, we got new messages
|
|
654
670
|
// so we should scroll to bottom if we are near the bottom already
|
|
655
|
-
|
|
671
|
+
const shouldForceScrollToRecent =
|
|
672
|
+
!didMergeMessageSetsWithNoUpdates ||
|
|
673
|
+
processedMessageList.length - messageListLengthBeforeUpdate.current > 0;
|
|
674
|
+
|
|
675
|
+
setAutoscrollToRecent(shouldForceScrollToRecent);
|
|
656
676
|
|
|
657
677
|
if (!didMergeMessageSetsWithNoUpdates) {
|
|
658
678
|
const shouldScrollToRecentOnNewOwnMessage = shouldScrollToRecentOnNewOwnMessageRef.current();
|
|
@@ -667,8 +687,7 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
|
|
|
667
687
|
}, WAIT_FOR_SCROLL_TIMEOUT); // flatlist might take a bit to update, so a small delay is needed
|
|
668
688
|
}
|
|
669
689
|
}
|
|
670
|
-
|
|
671
|
-
}, [channel, processedMessageList, threadList]);
|
|
690
|
+
}, [channel, threadList, processedMessageList, shouldScrollToRecentOnNewOwnMessageRef]);
|
|
672
691
|
|
|
673
692
|
const goToMessage = useStableCallback(async (messageId: string) => {
|
|
674
693
|
const indexOfParentInMessageList = processedMessageList.findIndex(
|
|
@@ -1218,7 +1237,10 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
|
|
|
1218
1237
|
onViewableItemsChanged={stableOnViewableItemsChanged}
|
|
1219
1238
|
ref={refCallback}
|
|
1220
1239
|
renderItem={renderItem}
|
|
1240
|
+
scrollEventThrottle={isLiveStreaming ? 16 : undefined}
|
|
1221
1241
|
showsVerticalScrollIndicator={false}
|
|
1242
|
+
// @ts-expect-error react-native internal
|
|
1243
|
+
strictMode={isLiveStreaming}
|
|
1222
1244
|
style={flatListStyle}
|
|
1223
1245
|
testID='message-flat-list'
|
|
1224
1246
|
viewabilityConfig={flatListViewabilityConfig}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { usePaginatedMessageListContext } from '../../../contexts/paginatedMessageListContext/PaginatedMessageListContext';
|
|
12
12
|
import { useThreadContext } from '../../../contexts/threadContext/ThreadContext';
|
|
13
13
|
|
|
14
|
+
import { useRAFCoalescedValue } from '../../../hooks';
|
|
14
15
|
import { DateSeparators, getDateSeparators } from '../utils/getDateSeparators';
|
|
15
16
|
import { getGroupStyles } from '../utils/getGroupStyles';
|
|
16
17
|
|
|
@@ -18,6 +19,7 @@ export type UseMessageListParams = {
|
|
|
18
19
|
deletedMessagesVisibilityType?: DeletedMessagesVisibilityType;
|
|
19
20
|
noGroupByUser?: boolean;
|
|
20
21
|
threadList?: boolean;
|
|
22
|
+
isLiveStreaming?: boolean;
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
export type GroupType = string;
|
|
@@ -48,7 +50,7 @@ export const shouldIncludeMessageInList = (
|
|
|
48
50
|
};
|
|
49
51
|
|
|
50
52
|
export const useMessageList = (params: UseMessageListParams) => {
|
|
51
|
-
const { noGroupByUser, threadList } = params;
|
|
53
|
+
const { noGroupByUser, threadList, isLiveStreaming } = params;
|
|
52
54
|
const { client } = useChatContext();
|
|
53
55
|
const { hideDateSeparators, maxTimeBetweenGroupedMessages } = useChannelContext();
|
|
54
56
|
const { deletedMessagesVisibilityType, getMessagesGroupStyles = getGroupStyles } =
|
|
@@ -110,14 +112,19 @@ export const useMessageList = (params: UseMessageListParams) => {
|
|
|
110
112
|
return newMessageList;
|
|
111
113
|
}, [client.userID, deletedMessagesVisibilityType, messageList]);
|
|
112
114
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
115
|
+
const data = useRAFCoalescedValue(processedMessageList, isLiveStreaming);
|
|
116
|
+
|
|
117
|
+
return useMemo(
|
|
118
|
+
() => ({
|
|
119
|
+
/** Date separators */
|
|
120
|
+
dateSeparatorsRef,
|
|
121
|
+
/** Message group styles */
|
|
122
|
+
messageGroupStylesRef,
|
|
123
|
+
/** Messages enriched with dates/readby/groups and also reversed in order */
|
|
124
|
+
processedMessageList: data,
|
|
125
|
+
/** Raw messages from the channel state */
|
|
126
|
+
rawMessageList: messageList,
|
|
127
|
+
}),
|
|
128
|
+
[data, messageList],
|
|
129
|
+
);
|
|
123
130
|
};
|
package/src/hooks/index.ts
CHANGED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A utility hook that coalesces a fast changing value to the display’s frame rate.
|
|
5
|
+
* It accepts any “noisy” input (arrays, objects, numbers, etc.) and exposes a value
|
|
6
|
+
* that React consumers will see at most once per animation frame (via
|
|
7
|
+
* `requestAnimationFrame`). This is useful when upstream sources (selectors, sockets,
|
|
8
|
+
* DB listeners) can fire multiple times within a single paint and you want to avoid
|
|
9
|
+
* extra renders and layout churn.
|
|
10
|
+
*
|
|
11
|
+
* How it works:
|
|
12
|
+
* - Keeps track of the latest incoming value
|
|
13
|
+
* - Ensures there is **at most one** pending RAF at a time
|
|
14
|
+
* - When the RAF fires, commits the **latest** value to state (`emitted`)
|
|
15
|
+
* - If additional changes arrive before the RAF runs, they are merged (the last write
|
|
16
|
+
* operation wins) and no new RAF is scheduled
|
|
17
|
+
*
|
|
18
|
+
* With this hook you can:
|
|
19
|
+
* - Feed a `FlatList`/`SectionList` from fast changing sources without spamming re-renders
|
|
20
|
+
* - Align React updates to the paint cadence (one publish per frame)
|
|
21
|
+
* - Help preserve item anchoring logic (e.g., MVCP) by reducing in-frame updates
|
|
22
|
+
*
|
|
23
|
+
* **Caveats:**
|
|
24
|
+
* - This hook intentionally skips intermediate states that occur within the same
|
|
25
|
+
* frame. If you must observe every transition (e.g., for analytics/reducers), do that
|
|
26
|
+
* upstream; this hook is for visual coalescing
|
|
27
|
+
* - Equality checks are simple referential equalities. If your producer recreates arrays
|
|
28
|
+
* or objects each time, you’ll still publish once per frame. To avoid even those
|
|
29
|
+
* emissions, stabilize upstream
|
|
30
|
+
* - This is not a silver bullet for throttle/debounce; it uses the screen’s refresh cycle;
|
|
31
|
+
* If you need “no more than once per X ms”, layer that upstream
|
|
32
|
+
*
|
|
33
|
+
* Usage tips:
|
|
34
|
+
* - Prefer passing already-memoized values when possible (e.g., stable arrays by ID).
|
|
35
|
+
* - Pair with a stable `keyExtractor` in lists so coalesced updates map cleanly to rows.
|
|
36
|
+
* - Do not cancel/reschedule on prop changes; cancellation is handled on unmount only.
|
|
37
|
+
*
|
|
38
|
+
* @param value The upstream value that may change multiple times within a single frame.
|
|
39
|
+
* @param isEnabled Determines whether the hook should be run or not (useful for cases where
|
|
40
|
+
* we want to conditionally use RAF when certain feature feature flags are enabled). If `false`,
|
|
41
|
+
* it will simply pass the data through (maintaining the reference as well).
|
|
42
|
+
* @returns A value that updates **at most once per frame** with the latest input.
|
|
43
|
+
*/
|
|
44
|
+
export const useRAFCoalescedValue = <S>(value: S, isEnabled: boolean | undefined): S => {
|
|
45
|
+
const [emitted, setEmitted] = useState<S>(value);
|
|
46
|
+
const pendingRef = useRef<S>(value);
|
|
47
|
+
const rafIdRef = useRef<number | null>(null);
|
|
48
|
+
|
|
49
|
+
// If `value` changes, schedule a single RAF to publish the latest one.
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (value === pendingRef.current || !isEnabled) return;
|
|
52
|
+
pendingRef.current = value;
|
|
53
|
+
|
|
54
|
+
// already scheduled the next frame, skip
|
|
55
|
+
if (rafIdRef.current) return;
|
|
56
|
+
|
|
57
|
+
const run = () => {
|
|
58
|
+
rafIdRef.current = null;
|
|
59
|
+
setEmitted(pendingRef.current);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
rafIdRef.current = requestAnimationFrame(run);
|
|
63
|
+
}, [value, isEnabled]);
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
return () => {
|
|
67
|
+
// cancel the frame if it exists only on unmount
|
|
68
|
+
if (rafIdRef.current) {
|
|
69
|
+
cancelAnimationFrame(rafIdRef.current);
|
|
70
|
+
rafIdRef.current = null;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}, []);
|
|
74
|
+
|
|
75
|
+
return isEnabled ? emitted : value;
|
|
76
|
+
};
|
package/src/version.json
CHANGED