stream-chat-react-native-core 9.3.1-beta.2 → 9.3.1-beta.4

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.
Files changed (50) hide show
  1. package/lib/commonjs/components/AutoCompleteInput/AutoCompleteInput.js +38 -9
  2. package/lib/commonjs/components/AutoCompleteInput/AutoCompleteInput.js.map +1 -1
  3. package/lib/commonjs/components/Message/MessageItemView/MessageBubble.js +2 -3
  4. package/lib/commonjs/components/Message/MessageItemView/MessageBubble.js.map +1 -1
  5. package/lib/commonjs/components/Message/MessageItemView/MessageContent.js +18 -29
  6. package/lib/commonjs/components/Message/MessageItemView/MessageContent.js.map +1 -1
  7. package/lib/commonjs/components/Message/MessageItemView/MessageWrapper.js +2 -3
  8. package/lib/commonjs/components/Message/MessageItemView/MessageWrapper.js.map +1 -1
  9. package/lib/commonjs/components/Message/MessageItemView/utils/renderText.js +24 -0
  10. package/lib/commonjs/components/Message/MessageItemView/utils/renderText.js.map +1 -1
  11. package/lib/commonjs/components/Message/hooks/useMessageDeliveryData.js +10 -5
  12. package/lib/commonjs/components/Message/hooks/useMessageDeliveryData.js.map +1 -1
  13. package/lib/commonjs/components/Message/hooks/useMessageReadData.js +10 -5
  14. package/lib/commonjs/components/Message/hooks/useMessageReadData.js.map +1 -1
  15. package/lib/commonjs/version.json +1 -1
  16. package/lib/module/components/AutoCompleteInput/AutoCompleteInput.js +38 -9
  17. package/lib/module/components/AutoCompleteInput/AutoCompleteInput.js.map +1 -1
  18. package/lib/module/components/Message/MessageItemView/MessageBubble.js +2 -3
  19. package/lib/module/components/Message/MessageItemView/MessageBubble.js.map +1 -1
  20. package/lib/module/components/Message/MessageItemView/MessageContent.js +18 -29
  21. package/lib/module/components/Message/MessageItemView/MessageContent.js.map +1 -1
  22. package/lib/module/components/Message/MessageItemView/MessageWrapper.js +2 -3
  23. package/lib/module/components/Message/MessageItemView/MessageWrapper.js.map +1 -1
  24. package/lib/module/components/Message/MessageItemView/utils/renderText.js +24 -0
  25. package/lib/module/components/Message/MessageItemView/utils/renderText.js.map +1 -1
  26. package/lib/module/components/Message/hooks/useMessageDeliveryData.js +10 -5
  27. package/lib/module/components/Message/hooks/useMessageDeliveryData.js.map +1 -1
  28. package/lib/module/components/Message/hooks/useMessageReadData.js +10 -5
  29. package/lib/module/components/Message/hooks/useMessageReadData.js.map +1 -1
  30. package/lib/module/version.json +1 -1
  31. package/lib/typescript/components/AutoCompleteInput/AutoCompleteInput.d.ts.map +1 -1
  32. package/lib/typescript/components/Message/MessageItemView/MessageBubble.d.ts +1 -1
  33. package/lib/typescript/components/Message/MessageItemView/MessageBubble.d.ts.map +1 -1
  34. package/lib/typescript/components/Message/MessageItemView/MessageContent.d.ts.map +1 -1
  35. package/lib/typescript/components/Message/MessageItemView/MessageWrapper.d.ts +1 -1
  36. package/lib/typescript/components/Message/MessageItemView/MessageWrapper.d.ts.map +1 -1
  37. package/lib/typescript/components/Message/MessageItemView/utils/renderText.d.ts.map +1 -1
  38. package/lib/typescript/components/Message/hooks/useMessageDeliveryData.d.ts.map +1 -1
  39. package/lib/typescript/components/Message/hooks/useMessageReadData.d.ts.map +1 -1
  40. package/package.json +1 -1
  41. package/src/components/AutoCompleteInput/AutoCompleteInput.tsx +51 -11
  42. package/src/components/AutoCompleteInput/__tests__/AutoCompleteInput.test.tsx +118 -0
  43. package/src/components/Message/MessageItemView/MessageBubble.tsx +3 -1
  44. package/src/components/Message/MessageItemView/MessageContent.tsx +32 -36
  45. package/src/components/Message/MessageItemView/MessageWrapper.tsx +1 -1
  46. package/src/components/Message/MessageItemView/utils/renderText.tsx +31 -0
  47. package/src/components/Message/hooks/useMessageDeliveryData.ts +11 -6
  48. package/src/components/Message/hooks/useMessageReadData.ts +11 -6
  49. package/src/components/Thread/__tests__/__snapshots__/Thread.test.tsx.snap +4 -4
  50. package/src/version.json +1 -1
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
1
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
2
  import {
3
3
  I18nManager,
4
4
  Platform,
@@ -109,6 +109,42 @@ const AutoCompleteInputWithContext = (props: AutoCompleteInputPropsWithContext)
109
109
  const { command, text } = useStateStore(textComposer.state, textComposerStateSelector);
110
110
  const { enabled } = useStateStore(messageComposer.configState, configStateSelector);
111
111
 
112
+ // RN's onChangeText doesn't carry cursor info, and iOS / Android fire
113
+ // onChangeText vs onSelectionChange in different orders. Rather than derive
114
+ // the caret from a text-length delta (fragile — gets clobbered by re-renders
115
+ // and varies across platforms), we hold the latest values reported by native
116
+ // and call into the LLC once both have settled.
117
+ const latestTextRef = useRef('');
118
+ const latestSelectionRef = useRef<{ end: number; start: number }>({
119
+ end: 0,
120
+ start: 0,
121
+ });
122
+ const flushHandleRef = useRef<ReturnType<typeof setTimeout> | null>(null);
123
+
124
+ const flushChange = useCallback(() => {
125
+ flushHandleRef.current = null;
126
+ textComposer.handleChange({
127
+ selection: latestSelectionRef.current,
128
+ text: latestTextRef.current,
129
+ });
130
+ }, [textComposer]);
131
+
132
+ // Defer to the next task so onChangeText and onSelectionChange both land
133
+ // before we forward to the LLC, regardless of platform ordering.
134
+ const scheduleChange = useCallback(() => {
135
+ if (flushHandleRef.current !== null) return;
136
+ flushHandleRef.current = setTimeout(flushChange, 0);
137
+ }, [flushChange]);
138
+
139
+ useEffect(() => {
140
+ return () => {
141
+ if (flushHandleRef.current !== null) {
142
+ clearTimeout(flushHandleRef.current);
143
+ flushHandleRef.current = null;
144
+ }
145
+ };
146
+ }, []);
147
+
112
148
  const maxMessageLength = useMemo(() => {
113
149
  return channel.getConfig()?.max_message_length;
114
150
  }, [channel]);
@@ -119,6 +155,14 @@ const AutoCompleteInputWithContext = (props: AutoCompleteInputPropsWithContext)
119
155
 
120
156
  useEffect(() => {
121
157
  setLocalText(text);
158
+ // Only resync the refs when the text change came from outside (clear after
159
+ // send, draft restore, programmatic setText). For changes we triggered
160
+ // ourselves, latestTextRef is already up to date and overwriting the
161
+ // selection would clobber what onSelectionChange just told us.
162
+ if (text !== latestTextRef.current) {
163
+ latestTextRef.current = text;
164
+ latestSelectionRef.current = { end: text.length, start: text.length };
165
+ }
122
166
  }, [text]);
123
167
 
124
168
  const clearState = useCallback(() => {
@@ -148,24 +192,20 @@ const AutoCompleteInputWithContext = (props: AutoCompleteInputPropsWithContext)
148
192
  const handleSelectionChange = useCallback(
149
193
  (e: TextInputSelectionChangeEvent) => {
150
194
  const { selection } = e.nativeEvent;
195
+ latestSelectionRef.current = selection;
151
196
  textComposer.setSelection(selection);
197
+ scheduleChange();
152
198
  },
153
- [textComposer],
199
+ [scheduleChange, textComposer],
154
200
  );
155
201
 
156
202
  const onChangeTextHandler = useCallback(
157
203
  (newText: string) => {
158
204
  setLocalText(newText);
159
-
160
- textComposer.handleChange({
161
- selection: {
162
- end: newText.length,
163
- start: newText.length,
164
- },
165
- text: newText,
166
- });
205
+ latestTextRef.current = newText;
206
+ scheduleChange();
167
207
  },
168
- [textComposer],
208
+ [scheduleChange],
169
209
  );
170
210
 
171
211
  const {
@@ -112,8 +112,13 @@ describe('AutoCompleteInput', () => {
112
112
 
113
113
  const input = queryByTestId('auto-complete-text-input')!;
114
114
 
115
+ // RN fires both events for every keystroke; we forward to the LLC once
116
+ // both have settled, so the test mirrors that.
115
117
  act(() => {
116
118
  fireEvent.changeText(input, 'hello');
119
+ fireEvent(input, 'selectionChange', {
120
+ nativeEvent: { selection: { end: 5, start: 5 } },
121
+ });
117
122
  });
118
123
 
119
124
  await waitFor(() => {
@@ -170,6 +175,119 @@ describe('AutoCompleteInput', () => {
170
175
  });
171
176
  });
172
177
 
178
+ it('forwards the real caret position to handleChange when typing in the middle of multi-line text', async () => {
179
+ // Regression: when the user inserts a character somewhere other than the
180
+ // end of the text (e.g. typing "@" between paragraphs), the original
181
+ // implementation passed selection.end = newText.length, which caused the
182
+ // LLC mention-trigger regex to miss "@" on multi-line input because it
183
+ // only tolerates one whitespace between "@" and end-of-string.
184
+ const { textComposer } = channel.messageComposer;
185
+ const spyHandleChange = jest.spyOn(textComposer, 'handleChange');
186
+
187
+ renderComponent({ channelProps: { channel }, client, props: {} });
188
+
189
+ const input = screen.getByTestId('auto-complete-text-input');
190
+
191
+ // Seed the input with some multi-line text and place the caret between
192
+ // the two leading newlines (position 6 — right after "asdf\n\n", before
193
+ // the trailing "\n\n dsfa").
194
+ const seeded = 'asdf\n\n\n\n dsfa';
195
+ const caret = 6;
196
+ act(() => {
197
+ fireEvent.changeText(input, seeded);
198
+ fireEvent(input, 'selectionChange', {
199
+ nativeEvent: { selection: { end: caret, start: caret } },
200
+ });
201
+ });
202
+
203
+ await waitFor(() => {
204
+ expect(spyHandleChange).toHaveBeenCalledWith({
205
+ selection: { end: caret, start: caret },
206
+ text: seeded,
207
+ });
208
+ });
209
+
210
+ spyHandleChange.mockClear();
211
+
212
+ // User types "@" at the caret. Both events fire in a single keystroke.
213
+ const inserted = 'asdf\n\n@\n\n dsfa';
214
+ const newCaret = caret + 1;
215
+ act(() => {
216
+ fireEvent.changeText(input, inserted);
217
+ fireEvent(input, 'selectionChange', {
218
+ nativeEvent: { selection: { end: newCaret, start: newCaret } },
219
+ });
220
+ });
221
+
222
+ await waitFor(() => {
223
+ // Must land right after the inserted "@", not at end-of-string —
224
+ // otherwise the LLC mention regex misses "@" because of the trailing
225
+ // "\n\n dsfa".
226
+ expect(spyHandleChange).toHaveBeenCalledWith({
227
+ selection: { end: newCaret, start: newCaret },
228
+ text: inserted,
229
+ });
230
+ });
231
+ });
232
+
233
+ it('forwards the real caret to handleChange when the user deletes "@" and retypes it', async () => {
234
+ // Regression: deleting "@" and retyping it on the same single line caused
235
+ // the picker to stay hidden on iOS — and on Android even with newlines.
236
+ // The bug was that we derived the caret from a text-length delta plus a
237
+ // stale ref; the coalesced flush now uses whatever native actually
238
+ // reported via onSelectionChange.
239
+ const { textComposer } = channel.messageComposer;
240
+ const spyHandleChange = jest.spyOn(textComposer, 'handleChange');
241
+
242
+ renderComponent({ channelProps: { channel }, client, props: {} });
243
+
244
+ const input = screen.getByTestId('auto-complete-text-input');
245
+
246
+ // 1. Seed "asdf @ dsfa" with the caret right after "@".
247
+ act(() => {
248
+ fireEvent.changeText(input, 'asdf @ dsfa');
249
+ fireEvent(input, 'selectionChange', {
250
+ nativeEvent: { selection: { end: 6, start: 6 } },
251
+ });
252
+ });
253
+
254
+ await waitFor(() => {
255
+ expect(spyHandleChange).toHaveBeenCalledWith({
256
+ selection: { end: 6, start: 6 },
257
+ text: 'asdf @ dsfa',
258
+ });
259
+ });
260
+
261
+ // 2. Delete the "@" — text shrinks by one, caret moves to position 5.
262
+ act(() => {
263
+ fireEvent.changeText(input, 'asdf dsfa');
264
+ fireEvent(input, 'selectionChange', {
265
+ nativeEvent: { selection: { end: 5, start: 5 } },
266
+ });
267
+ });
268
+
269
+ spyHandleChange.mockClear();
270
+
271
+ // 3. Retype "@" at position 5.
272
+ act(() => {
273
+ fireEvent.changeText(input, 'asdf @ dsfa');
274
+ fireEvent(input, 'selectionChange', {
275
+ nativeEvent: { selection: { end: 6, start: 6 } },
276
+ });
277
+ });
278
+
279
+ await waitFor(() => {
280
+ // The cursor must be reported at 6 (right after the new "@"), not
281
+ // somewhere stale. With a wrong caret, the LLC slice would include
282
+ // " dsfa" after the "@" and the query would be " dsfa" instead of "",
283
+ // which returns zero users → picker stays hidden.
284
+ expect(spyHandleChange).toHaveBeenCalledWith({
285
+ selection: { end: 6, start: 6 },
286
+ text: 'asdf @ dsfa',
287
+ });
288
+ });
289
+ });
290
+
173
291
  it('should call the textComposer setSelection when the onSelectionChange is triggered', async () => {
174
292
  const { textComposer } = channel.messageComposer;
175
293
 
@@ -27,7 +27,9 @@ type SwipableMessageWrapperProps = Pick<
27
27
  onSwipe: () => void;
28
28
  };
29
29
 
30
- export const SwipableMessageWrapper = React.memo((props: SwipableMessageWrapperProps) => {
30
+ export const SwipableMessageWrapper = React.memo(function SwipableMessageWrapper(
31
+ props: SwipableMessageWrapperProps,
32
+ ) {
31
33
  const { children, messageSwipeToReplyHitSlop, onSwipe } = props;
32
34
  const { MessageSwipeContent } = useComponentsContext();
33
35
  const isRTL = I18nManager.isRTL;
@@ -1,5 +1,5 @@
1
1
  import React, { useMemo } from 'react';
2
- import { AnimatableNumericValue, ColorValue, Pressable, StyleSheet, View } from 'react-native';
2
+ import { ColorValue, Pressable, StyleSheet, View, ViewStyle } from 'react-native';
3
3
 
4
4
  import { MessageTextContainer } from './MessageTextContainer';
5
5
 
@@ -169,47 +169,46 @@ const MessageContentWithContext = (props: MessageContentPropsWithContext) => {
169
169
  [message, isMessageAIGenerated],
170
170
  );
171
171
 
172
- const getBorderRadius = () => {
172
+ // Merged background-color + border-radius object passed directly into the
173
+ // bubble's style array (no spread at the call site). Theme-defined radii
174
+ // override the group-position-computed defaults; theme-undefined radii are
175
+ // omitted so they don't override the computed defaults.
176
+ const bubbleColorAndRadius = useMemo<ViewStyle>(() => {
173
177
  // enum('top', 'middle', 'bottom', 'single')
174
178
  const groupPosition = groupStyles?.[0];
175
-
176
179
  const isBottomOrSingle = groupPosition === 'single' || groupPosition === 'bottom';
177
- let borderBottomLeftRadius = components.messageBubbleRadiusGroupBottom;
178
- let borderBottomRightRadius = components.messageBubbleRadiusGroupBottom;
179
180
 
181
+ let computedBottomLeftRadius = components.messageBubbleRadiusGroupBottom;
182
+ let computedBottomRightRadius = components.messageBubbleRadiusGroupBottom;
180
183
  if (isBottomOrSingle) {
181
- // add relevant sharp corner
184
+ // add relevant sharp corner (the "tail")
182
185
  if (isMyMessage) {
183
- borderBottomRightRadius = components.messageBubbleRadiusTail;
186
+ computedBottomRightRadius = components.messageBubbleRadiusTail;
184
187
  } else {
185
- borderBottomLeftRadius = components.messageBubbleRadiusTail;
188
+ computedBottomLeftRadius = components.messageBubbleRadiusTail;
186
189
  }
187
190
  }
188
191
 
189
- return {
190
- borderBottomLeftRadius,
191
- borderBottomRightRadius,
192
- };
193
- };
194
-
195
- const getBorderRadiusFromTheme = () => {
196
- const bordersFromTheme: Record<string, string | AnimatableNumericValue | undefined> = {
197
- borderBottomLeftRadius,
198
- borderBottomRightRadius,
199
- borderRadius,
200
- borderTopLeftRadius,
201
- borderTopRightRadius,
192
+ const style: ViewStyle = {
193
+ backgroundColor,
194
+ borderBottomLeftRadius: borderBottomLeftRadius ?? computedBottomLeftRadius,
195
+ borderBottomRightRadius: borderBottomRightRadius ?? computedBottomRightRadius,
202
196
  };
197
+ if (borderRadius !== undefined) style.borderRadius = borderRadius;
198
+ if (borderTopLeftRadius !== undefined) style.borderTopLeftRadius = borderTopLeftRadius;
199
+ if (borderTopRightRadius !== undefined) style.borderTopRightRadius = borderTopRightRadius;
203
200
 
204
- // filter out undefined values
205
- for (const key in bordersFromTheme) {
206
- if (bordersFromTheme[key] === undefined) {
207
- delete bordersFromTheme[key];
208
- }
209
- }
210
-
211
- return bordersFromTheme;
212
- };
201
+ return style;
202
+ }, [
203
+ backgroundColor,
204
+ borderBottomLeftRadius,
205
+ borderBottomRightRadius,
206
+ borderRadius,
207
+ borderTopLeftRadius,
208
+ borderTopRightRadius,
209
+ groupStyles,
210
+ isMyMessage,
211
+ ]);
213
212
 
214
213
  const { setNativeScrollability } = useMessageListItemContext();
215
214
  const hasContentSideViews = !!(MessageContentLeadingView || MessageContentTrailingView);
@@ -357,12 +356,8 @@ const MessageContentWithContext = (props: MessageContentPropsWithContext) => {
357
356
  <View
358
357
  style={[
359
358
  styles.containerInner,
360
- {
361
- backgroundColor,
362
- ...getBorderRadius(),
363
- ...getBorderRadiusFromTheme(),
364
- },
365
- noBorder ? { borderWidth: 0 } : {},
359
+ bubbleColorAndRadius,
360
+ noBorder ? styles.noBorder : null,
366
361
  containerInner,
367
362
  messageGroupedSingleOrBottom
368
363
  ? isVeryLastMessage && enableMessageGroupingByUser
@@ -684,6 +679,7 @@ const styles = StyleSheet.create({
684
679
  alignSelf: 'center',
685
680
  },
686
681
  galleryContainer: {},
682
+ noBorder: { borderWidth: 0 },
687
683
  rightAlignContent: {
688
684
  justifyContent: 'flex-end',
689
685
  },
@@ -30,7 +30,7 @@ export type MessageWrapperProps = {
30
30
  nextMessage?: LocalMessage;
31
31
  };
32
32
 
33
- export const MessageWrapper = React.memo((props: MessageWrapperProps) => {
33
+ export const MessageWrapper = React.memo(function MessageWrapper(props: MessageWrapperProps) {
34
34
  const { message, previousMessage, nextMessage } = props;
35
35
  const { client } = useChatContext();
36
36
  const {
@@ -101,6 +101,37 @@ const defaultMarkdownStyles: MarkdownStyle = {
101
101
  fontSize: primitives.typographyFontSizeMd,
102
102
  lineHeight: primitives.typographyLineHeightNormal,
103
103
  },
104
+ // Heading sizes are derived from the body font size (`typographyFontSizeMd`) so they
105
+ // scale with the integrator's typography settings. lineHeight = fontSize × 1.25 to
106
+ // give headings room to breathe. Both fields are required here: without lineHeight,
107
+ // the inherited `lineHeight: typographyLineHeightNormal` (20) from `styles.text` (set
108
+ // in renderText below) leaks into the heading's inner Text via the markdown library's
109
+ // text rule (`{...styles.text, ...state.style}`) and squishes larger heading fontSizes
110
+ // into a 20px line box.
111
+ heading1: {
112
+ fontSize: primitives.typographyFontSizeMd * 2,
113
+ lineHeight: primitives.typographyFontSizeMd * 2 * 1.25,
114
+ },
115
+ heading2: {
116
+ fontSize: primitives.typographyFontSizeMd * 1.5,
117
+ lineHeight: primitives.typographyFontSizeMd * 1.5 * 1.25,
118
+ },
119
+ heading3: {
120
+ fontSize: primitives.typographyFontSizeMd * 1.25,
121
+ lineHeight: primitives.typographyFontSizeMd * 1.25 * 1.25,
122
+ },
123
+ heading4: {
124
+ fontSize: primitives.typographyFontSizeMd,
125
+ lineHeight: primitives.typographyFontSizeMd * 1.25,
126
+ },
127
+ heading5: {
128
+ fontSize: primitives.typographyFontSizeMd * 0.875,
129
+ lineHeight: primitives.typographyFontSizeMd * 0.875 * 1.25,
130
+ },
131
+ heading6: {
132
+ fontSize: primitives.typographyFontSizeMd * 0.75,
133
+ lineHeight: primitives.typographyFontSizeMd * 0.75 * 1.25,
134
+ },
104
135
  inlineCode: {
105
136
  padding: primitives.spacingXxs,
106
137
  paddingHorizontal: primitives.spacingXxs,
@@ -1,14 +1,18 @@
1
- import { useCallback, useEffect, useState } from 'react';
1
+ import { useEffect, useRef, useState } from 'react';
2
2
 
3
3
  import { Event, LocalMessage, UserResponse } from 'stream-chat';
4
4
 
5
5
  import { useChannelContext } from '../../../contexts/channelContext/ChannelContext';
6
6
  import { useChatContext } from '../../../contexts/chatContext/ChatContext';
7
+ import { useStableCallback } from '../../../hooks';
7
8
 
8
9
  export const useMessageDeliveredData = ({ message }: { message?: LocalMessage }) => {
9
10
  const { channel } = useChannelContext();
10
11
  const { client } = useChatContext();
11
- const calculate = useCallback(() => {
12
+
13
+ const messageIdRef = useRef<string>(message?.id);
14
+
15
+ const calculate = useStableCallback(() => {
12
16
  if (!message?.created_at) {
13
17
  return [];
14
18
  }
@@ -17,13 +21,14 @@ export const useMessageDeliveredData = ({ message }: { message?: LocalMessage })
17
21
  timestampMs: new Date(message.created_at).getTime(),
18
22
  };
19
23
  return channel.messageReceiptsTracker.deliveredForMessage(messageRef);
20
- }, [channel, message]);
24
+ });
21
25
 
22
- const [deliveredTo, setDeliveredTo] = useState<UserResponse[]>([]);
26
+ const [deliveredTo, setDeliveredTo] = useState<UserResponse[]>(() => calculate());
23
27
 
24
- useEffect(() => {
28
+ if (!!messageIdRef.current && !!message?.id && messageIdRef.current !== message.id) {
25
29
  setDeliveredTo(calculate());
26
- }, [calculate]);
30
+ messageIdRef.current = message.id;
31
+ }
27
32
 
28
33
  useEffect(() => {
29
34
  const { unsubscribe } = channel.on('message.delivered', (event: Event) => {
@@ -1,14 +1,18 @@
1
- import { useCallback, useEffect, useState } from 'react';
1
+ import { useEffect, useRef, useState } from 'react';
2
2
 
3
3
  import { Event, LocalMessage, UserResponse } from 'stream-chat';
4
4
 
5
5
  import { useChannelContext } from '../../../contexts/channelContext/ChannelContext';
6
6
  import { useChatContext } from '../../../contexts/chatContext/ChatContext';
7
+ import { useStableCallback } from '../../../hooks';
7
8
 
8
9
  export const useMessageReadData = ({ message }: { message?: LocalMessage }) => {
9
10
  const { channel } = useChannelContext();
10
11
  const { client } = useChatContext();
11
- const calculate = useCallback(() => {
12
+
13
+ const messageIdRef = useRef<string>(message?.id);
14
+
15
+ const calculate = useStableCallback(() => {
12
16
  if (!message?.created_at) {
13
17
  return [];
14
18
  }
@@ -18,13 +22,14 @@ export const useMessageReadData = ({ message }: { message?: LocalMessage }) => {
18
22
  };
19
23
 
20
24
  return channel.messageReceiptsTracker.readersForMessage(messageRef);
21
- }, [channel, message]);
25
+ });
22
26
 
23
- const [readBy, setReadBy] = useState<UserResponse[]>([]);
27
+ const [readBy, setReadBy] = useState<UserResponse[]>(() => calculate());
24
28
 
25
- useEffect(() => {
29
+ if (!!messageIdRef.current && !!message?.id && messageIdRef.current !== message.id) {
26
30
  setReadBy(calculate());
27
- }, [calculate]);
31
+ messageIdRef.current = message.id;
32
+ }
28
33
 
29
34
  useEffect(() => {
30
35
  const { unsubscribe } = channel.on('message.read', (event: Event) => {
@@ -511,7 +511,7 @@ exports[`Thread should match thread snapshot 1`] = `
511
511
  "borderBottomLeftRadius": 0,
512
512
  "borderBottomRightRadius": 20,
513
513
  },
514
- {},
514
+ null,
515
515
  {},
516
516
  {},
517
517
  ]
@@ -843,7 +843,7 @@ exports[`Thread should match thread snapshot 1`] = `
843
843
  "borderBottomLeftRadius": 0,
844
844
  "borderBottomRightRadius": 20,
845
845
  },
846
- {},
846
+ null,
847
847
  {},
848
848
  {},
849
849
  ]
@@ -1208,7 +1208,7 @@ exports[`Thread should match thread snapshot 1`] = `
1208
1208
  "borderBottomLeftRadius": 0,
1209
1209
  "borderBottomRightRadius": 20,
1210
1210
  },
1211
- {},
1211
+ null,
1212
1212
  {},
1213
1213
  {},
1214
1214
  ]
@@ -1534,7 +1534,7 @@ exports[`Thread should match thread snapshot 1`] = `
1534
1534
  "borderBottomLeftRadius": 0,
1535
1535
  "borderBottomRightRadius": 20,
1536
1536
  },
1537
- {},
1537
+ null,
1538
1538
  {},
1539
1539
  {},
1540
1540
  ]
package/src/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "9.3.1-beta.2"
2
+ "version": "9.3.1-beta.4"
3
3
  }