@messenger-box/platform-mobile 10.0.3-alpha.46 → 10.0.3-alpha.48

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.
@@ -15,11 +15,7 @@ import {
15
15
  import { format, isToday, isYesterday } from 'date-fns';
16
16
  import { useFocusEffect } from '@react-navigation/native';
17
17
  import { IChannel, SortEnum, IUserAccount } from 'common';
18
- import {
19
- CHAT_MESSAGE_ADDED,
20
- useChatMessageAddedSubscription,
21
- useMessagesQuery as useMessagesQueryFromInbox,
22
- } from '../../../queries/inboxQueries';
18
+ import { CHAT_MESSAGE_ADDED, useMessagesQuery as useMessagesQueryFromInbox } from '../../../queries/inboxQueries';
23
19
  import { startCase } from 'lodash-es';
24
20
  import colors from 'tailwindcss/colors';
25
21
  import { SubscriptionHandler } from './SubscriptionHandler';
@@ -47,30 +43,7 @@ export interface IDialogListItemProps {
47
43
  }
48
44
 
49
45
  // LastMessage component definition
50
- const LastMessageComponent = ({ subscribeToNewMessages, title, lastMessage, channelId, subscribeToMore }) => {
51
- // Subscribe to new messages when component mounts
52
- React.useEffect(() => {
53
- // Subscribe and store the unsubscribe function
54
- const unsubscribe = subscribeToNewMessages();
55
- return () => {
56
- // Cleanup subscription on unmount
57
- if (unsubscribe && typeof unsubscribe === 'function') {
58
- unsubscribe();
59
- }
60
- };
61
- }, [channelId, subscribeToNewMessages]);
62
-
63
- // Debug output for component rendering
64
- // React.useEffect(() => {
65
- // console.log(`LastMessageComponent rendered for channel ${channelId}:`, {
66
- // hasLastMessage: !!lastMessage,
67
- // messageId: lastMessage?.id,
68
- // messageText: lastMessage?.message?.substring(0, 20) + (lastMessage?.message?.length > 20 ? '...' : ''),
69
- // date: lastMessage?.createdAt ? new Date(lastMessage.createdAt).toISOString() : 'none',
70
- // hasFiles: lastMessage?.files?.data?.length > 0,
71
- // });
72
- // }, [lastMessage, channelId]);
73
-
46
+ const LastMessageComponent = ({ title, lastMessage }) => {
74
47
  const count = 30;
75
48
  const channelTitle = title?.slice(0, count) + (title?.length > count ? '...' : '') || '';
76
49
 
@@ -98,28 +71,20 @@ const LastMessageComponent = ({ subscribeToNewMessages, title, lastMessage, chan
98
71
  : '';
99
72
 
100
73
  return (
101
- <>
102
- <SubscriptionHandler
103
- subscribeToMore={subscribeToMore}
104
- document={CHAT_MESSAGE_ADDED}
105
- variables={{ channelId: channelId?.toString() }}
106
- updateQuery={undefined} // Provide custom updateQuery if needed
107
- />
108
- <HStack space={'sm'} className="flex-1 justify-between">
109
- <Box className="flex-[0.8]">
110
- <Text color={colors.gray[600]} className="text-base text-wrap flex-wrap font-semibold">
111
- {channelTitle}
112
- </Text>
113
- <Text color={colors.gray[600]} numberOfLines={1}>
114
- {displayMessage}
115
- </Text>
116
- </Box>
117
-
118
- <Box className="flex-[0.2]">
119
- <Text color={colors.gray[500]}>{displayDate}</Text>
120
- </Box>
121
- </HStack>
122
- </>
74
+ <HStack space={'sm'} className="flex-1 justify-between">
75
+ <Box className="flex-[0.8]">
76
+ <Text color={colors.gray[600]} className="text-base text-wrap flex-wrap font-semibold">
77
+ {channelTitle}
78
+ </Text>
79
+ <Text color={colors.gray[600]} numberOfLines={1}>
80
+ {displayMessage}
81
+ </Text>
82
+ </Box>
83
+
84
+ <Box className="flex-[0.2]">
85
+ <Text color={colors.gray[500]}>{displayDate}</Text>
86
+ </Box>
87
+ </HStack>
123
88
  );
124
89
  };
125
90
 
@@ -130,7 +95,6 @@ const LastMessageComponent = ({ subscribeToNewMessages, title, lastMessage, chan
130
95
  */
131
96
  export const DialogsListItemComponent: React.FC<IDialogListItemProps> = function DialogsListItem({
132
97
  currentUser,
133
- // users,
134
98
  selectedChannelId,
135
99
  channel,
136
100
  onOpen,
@@ -143,58 +107,91 @@ export const DialogsListItemComponent: React.FC<IDialogListItemProps> = function
143
107
  // Define parentId early to avoid linter errors
144
108
  const parentId: any = null;
145
109
 
146
- // State for component data
110
+ // Simplified state - only keep loading state, rely on Apollo cache for data
147
111
  const [loading, setLoading] = useState(false);
148
112
  const [title, setTitle] = useState('');
149
- const [messages, setMessages] = useState([]);
150
- const [lastMessage, setLastMessage] = useState(null);
151
113
 
152
114
  // Only run queries/subscriptions if visible
153
115
  const shouldQuery = !!channel?.id && visible;
154
116
 
155
- // Query hooks for fetching messages (minimal fields if possible)
117
+ // Memoize query variables to prevent unnecessary re-renders
118
+ const queryVariables = useMemo(
119
+ () => ({
120
+ channelId: channel?.id?.toString(),
121
+ parentId: parentId,
122
+ limit: 10,
123
+ }),
124
+ [channel?.id, parentId],
125
+ );
126
+
127
+ // Optimized message processing function
128
+ const processMessagesData = useCallback((messagesData: any[]) => {
129
+ if (!messagesData?.length) return null;
130
+
131
+ // Sort messages and return the latest one
132
+ const sortedMessages = [...messagesData].sort(
133
+ (a, b) =>
134
+ new Date(b?.updatedAt || b?.createdAt).getTime() - new Date(a?.updatedAt || a?.createdAt).getTime(),
135
+ );
136
+
137
+ return sortedMessages[0];
138
+ }, []);
139
+
140
+ // Optimized refetch function - consolidated all refetch logic
141
+ const optimizedRefetch = useCallback(
142
+ async (reason: string = 'manual') => {
143
+ if (!shouldQuery || !channel?.id || !isMountedRef.current) return null;
144
+
145
+ console.log(`Optimized refetch triggered: ${reason} for channel ${channel?.id}`);
146
+ setLoading(true);
147
+
148
+ try {
149
+ const result = await refetchMessages(queryVariables);
150
+ console.log(
151
+ `Refetch completed: ${reason}, messages count: ${result?.data?.messages?.data?.length || 0}`,
152
+ );
153
+ return result;
154
+ } catch (error) {
155
+ console.error(`Refetch error (${reason}):`, error);
156
+ return null;
157
+ } finally {
158
+ if (isMountedRef.current) {
159
+ setLoading(false);
160
+ }
161
+ }
162
+ },
163
+ [shouldQuery, channel?.id, queryVariables],
164
+ );
165
+
166
+ // Query hooks for fetching messages - optimized configuration
156
167
  const {
157
168
  data: messagesQuery,
158
169
  loading: messageLoading,
159
170
  refetch: refetchMessages,
160
171
  subscribeToMore,
172
+ error,
161
173
  } = useMessagesQueryFromInbox({
162
- variables: {
163
- channelId: channel?.id?.toString(),
164
- parentId: parentId,
165
- limit: 10,
166
- },
167
- skip: !shouldQuery, // Only run if visible
168
- fetchPolicy: 'cache-and-network',
169
- refetchWritePolicy: 'overwrite',
170
- nextFetchPolicy: 'network-only',
174
+ variables: queryVariables,
175
+ skip: !shouldQuery,
176
+ fetchPolicy: 'cache-first', // Changed from cache-and-network for better performance
177
+ nextFetchPolicy: 'cache-first',
178
+ notifyOnNetworkStatusChange: true,
179
+ errorPolicy: 'all', // Handle partial errors gracefully
171
180
  onCompleted: (data) => {
172
- if (!shouldQuery) return;
173
- if (data?.messages?.data) {
174
- setMessages(data.messages.data);
175
- const sortedMessages = [...data.messages.data].sort(
176
- (a, b) =>
177
- new Date(b?.updatedAt || b?.createdAt).getTime() -
178
- new Date(a?.updatedAt || a?.createdAt).getTime(),
179
- );
180
- if (sortedMessages.length > 0) {
181
- setLastMessage(sortedMessages[0]);
182
- }
183
- }
181
+ // Simplified onCompleted - minimal processing
182
+ if (!shouldQuery || !data?.messages?.data) return;
183
+
184
+ console.log(`Query completed for channel ${channel?.id}, messages: ${data.messages.data.length}`);
184
185
  },
185
186
  onError: (error) => {
186
187
  if (!shouldQuery) return;
187
- console.error(`Error fetching messages for channel ${channel?.id}:`, error);
188
+ console.error(`Query error for channel ${channel?.id}:`, error);
188
189
  },
189
190
  });
190
191
 
191
- // Subscription for new messages (only if visible)
192
- const { data: newMessage } = useChatMessageAddedSubscription({
193
- variables: {
194
- channelId: channel?.id?.toString(),
195
- },
196
- skip: !shouldQuery,
197
- });
192
+ // Derived state from query data - use Apollo cache as single source of truth
193
+ const messages = useMemo(() => messagesQuery?.messages?.data || [], [messagesQuery]);
194
+ const lastMessage = useMemo(() => processMessagesData(messages), [messages, processMessagesData]);
198
195
 
199
196
  // Set mounted state on mount/unmount
200
197
  useEffect(() => {
@@ -204,184 +201,56 @@ export const DialogsListItemComponent: React.FC<IDialogListItemProps> = function
204
201
  };
205
202
  }, []);
206
203
 
207
- // Function to force refresh the component state
208
- const refreshDialogState = useCallback(() => {
209
- if (shouldQuery && isMountedRef.current) {
210
- setLoading(true);
211
- const options = {
212
- channelId: channel?.id?.toString(),
213
- parentId: parentId,
214
- limit: 10,
215
- };
216
- refetchMessages(options)
217
- .then((result) => {
218
- if (result.data?.messages?.data && isMountedRef.current) {
219
- const sortedMessages = [...result.data.messages.data].sort(
220
- (a, b) =>
221
- new Date(b?.updatedAt || b?.createdAt).getTime() -
222
- new Date(a?.updatedAt || a?.createdAt).getTime(),
223
- );
224
- const latestMessage = sortedMessages.length > 0 ? sortedMessages[0] : null;
225
- setMessages(result.data.messages.data);
226
- setLastMessage(latestMessage);
227
- }
228
- setLoading(false);
229
- })
230
- .catch((err) => {
231
- console.error('Error refreshing dialog state:', err);
232
- setLoading(false);
233
- });
234
- }
235
- }, [shouldQuery, channel?.id, refetchMessages, parentId]);
236
-
237
- // Track if this is the first time the component renders
204
+ // Optimized focus effect - single refetch with debouncing
238
205
  const firstRenderRef = useRef(true);
239
-
240
- // Fix messages not refreshing when coming back from detail screen
241
206
  useFocusEffect(
242
- React.useCallback(() => {
207
+ useCallback(() => {
243
208
  if (!channel?.id) return;
244
209
 
245
210
  console.log('DialogsListItem focused for channel:', channel?.id);
246
211
 
247
- // Skip refresh on first render as it's handled by other effects
212
+ // Skip refresh on first render
248
213
  if (firstRenderRef.current) {
249
- console.log('Skipping initial focus refresh for channel:', channel?.id);
250
214
  firstRenderRef.current = false;
251
215
  return;
252
216
  }
253
217
 
254
- // Always force a refetch when coming back to this screen
255
- console.log('FOCUS EFFECT: Force refetching messages on navigation back for channel:', channel?.id);
256
-
257
- // Show loading state
258
- setLoading(true);
259
-
260
- // Use a direct refetch with network-only policy to force fresh data
261
- const fetchFreshData = async () => {
262
- try {
263
- // Set up the options for the query to force network fetch
264
- const options = {
265
- channelId: channel?.id?.toString(),
266
- parentId: parentId,
267
- limit: 10,
268
- };
269
-
270
- // Force a network-only fetch by using refetch without extra options
271
- // Apollo will use the parent query's fetch policy which we've set to network-only
272
- const result = await refetchMessages(options);
273
-
274
- // Log the refreshed data
275
- console.log(
276
- `FOCUS EFFECT: Refetched ${result?.data?.messages?.data?.length || 0} messages for channel ${
277
- channel?.id
278
- }`,
279
- );
280
-
281
- if (result?.data?.messages?.data && isMountedRef.current) {
282
- // Compare with current state to check if we're getting fresh data
283
- const currentMessages = messages;
284
- const fetchedMessages = result.data.messages.data;
285
-
286
- // Log comparison to see if we got new data
287
- console.log('Data comparison:', {
288
- currentCount: currentMessages.length,
289
- fetchedCount: fetchedMessages.length,
290
- isDifferent: JSON.stringify(currentMessages) !== JSON.stringify(fetchedMessages),
291
- });
292
-
293
- // Get the most recent message
294
- const sortedMessages = [...fetchedMessages].sort(
295
- (a, b) =>
296
- new Date(b?.updatedAt || b?.createdAt).getTime() -
297
- new Date(a?.updatedAt || a?.createdAt).getTime(),
298
- );
299
-
300
- const latestMessage = sortedMessages.length > 0 ? sortedMessages[0] : null;
301
-
302
- // Update state with fresh data
303
- setMessages(fetchedMessages);
304
- setLastMessage(latestMessage);
305
- }
306
- } catch (error) {
307
- console.error('Error refetching messages on focus:', error);
308
- } finally {
309
- if (isMountedRef.current) {
310
- setLoading(false);
311
- }
312
- }
313
- };
314
-
315
- // Execute fetch
316
- fetchFreshData();
218
+ // Debounced refetch on focus
219
+ const timeoutId = setTimeout(() => {
220
+ optimizedRefetch('focus');
221
+ }, 100);
317
222
 
318
- return () => {
319
- // Cleanup function when unfocused
320
- };
321
- }, [channel?.id, refetchMessages, messages, isMountedRef, parentId]),
223
+ return () => clearTimeout(timeoutId);
224
+ }, [channel?.id, optimizedRefetch]),
322
225
  );
323
226
 
324
- // Only run effects if visible
325
- useEffect(() => {
326
- if (!shouldQuery) return;
327
- if (channel?.id && isMountedRef.current) {
328
- const timer = setTimeout(() => {
329
- if (isMountedRef.current) {
330
- refreshDialogState();
331
- }
332
- }, 100);
333
- return () => {
334
- clearTimeout(timer);
335
- };
336
- }
337
- }, [shouldQuery, channel?.id, refreshDialogState]);
338
-
227
+ // Simplified effect for force refresh
339
228
  useEffect(() => {
340
- if (forceRefresh && shouldQuery && channel?.id && isMountedRef.current) {
341
- const timer = setTimeout(() => {
342
- if (isMountedRef.current && refetchMessages) {
343
- refetchMessages({
344
- channelId: channel?.id?.toString(),
345
- parentId: parentId,
346
- limit: 10,
347
- })
348
- .then((result) => {
349
- if (result?.data?.messages?.data && isMountedRef.current) {
350
- const sortedMessages = [...result.data.messages.data].sort(
351
- (a, b) =>
352
- new Date(b?.updatedAt || b?.createdAt).getTime() -
353
- new Date(a?.updatedAt || a?.createdAt).getTime(),
354
- );
355
- const latestMessage = sortedMessages.length > 0 ? sortedMessages[0] : null;
356
- setMessages(result.data.messages.data);
357
- setLastMessage(latestMessage);
358
- }
359
- })
360
- .catch((error) => {
361
- console.error(`Error force refreshing channel ${channel?.id}:`, error);
362
- });
363
- }
229
+ if (forceRefresh && shouldQuery) {
230
+ const timeoutId = setTimeout(() => {
231
+ optimizedRefetch('force');
364
232
  }, 50);
365
- return () => clearTimeout(timer);
233
+ return () => clearTimeout(timeoutId);
366
234
  }
367
- }, [shouldQuery, channel?.id, forceRefresh, refetchMessages, parentId]);
235
+ }, [forceRefresh, shouldQuery, optimizedRefetch]);
368
236
 
237
+ // Channel members computation - optimized with better memoization
369
238
  const channelMembers = useMemo(
370
239
  () =>
371
240
  channel?.members
372
- ?.filter((ch: any) => ch?.user?.id != currentUser?.id && ch?.user?.__typename == 'UserAccount')
373
- ?.map((m: any) => m?.user) ?? null,
374
- [currentUser, channel],
241
+ ?.filter((ch: any) => ch?.user?.id !== currentUser?.id && ch?.user?.__typename === 'UserAccount')
242
+ ?.map((m: any) => m?.user) ?? [],
243
+ [currentUser?.id, channel?.members],
375
244
  );
376
245
 
377
246
  // Set title when channel members change
378
247
  useEffect(() => {
379
- if (channelMembers && isMountedRef.current) {
248
+ if (channelMembers.length > 0 && isMountedRef.current) {
380
249
  const titleString =
381
250
  channelMembers
382
- ?.map((u: any) => u?.givenName + ' ' + (u?.familyName ?? ''))
383
- ?.filter((mu: any) => mu)
384
- ?.join(', ') ?? '';
251
+ ?.map((u: any) => `${u?.givenName || ''} ${u?.familyName || ''}`.trim())
252
+ ?.filter(Boolean)
253
+ ?.join(', ') || '';
385
254
  setTitle(titleString);
386
255
  }
387
256
  }, [channelMembers]);
@@ -392,38 +261,8 @@ export const DialogsListItemComponent: React.FC<IDialogListItemProps> = function
392
261
  return title.length > length ? title.substring(0, length - 3) + '...' : title;
393
262
  }, [title]);
394
263
 
395
- // Debug output for the component
396
- useEffect(() => {
397
- console.log(`DialogsListItem for channel ${channel?.id}: `, {
398
- hasLastMessage: !!lastMessage,
399
- message: lastMessage?.message?.substring(0, 20) + (lastMessage?.message?.length > 20 ? '...' : ''),
400
- messagesCount: messages.length,
401
- });
402
- }, [channel?.id, lastMessage, messages.length]);
403
-
404
- // Handle new messages from subscription
405
- useEffect(() => {
406
- if (!shouldQuery) return;
407
- if (newMessage?.chatMessageAdded && channel?.id) {
408
- const incomingMessage = newMessage.chatMessageAdded;
409
- if (messages.some((msg) => msg.id === incomingMessage.id)) {
410
- return;
411
- }
412
- setMessages((prevMessages) => [...prevMessages, incomingMessage]);
413
- setLastMessage(incomingMessage);
414
- }
415
- }, [shouldQuery, newMessage, channel?.id, messages]);
416
-
417
- // Create listener for channel property updates
418
- useEffect(() => {
419
- if (!shouldQuery) return;
420
- if (channel?.lastMessage && channel.lastMessage.id) {
421
- if (!messages.some((msg) => msg.id === channel.lastMessage.id)) {
422
- setMessages((prevMessages) => [...prevMessages, channel.lastMessage]);
423
- setLastMessage(channel.lastMessage);
424
- }
425
- }
426
- }, [shouldQuery, channel?.lastMessage?.id, messages]);
264
+ // Combined loading state
265
+ const isLoading = loading || messageLoading;
427
266
 
428
267
  return (
429
268
  <Pressable
@@ -431,7 +270,39 @@ export const DialogsListItemComponent: React.FC<IDialogListItemProps> = function
431
270
  className="flex-1 border-gray-200 rounded-md dark:border-gray-600 dark:bg-gray-700"
432
271
  style={{ borderBottomWidth: 1, borderColor: '#e5e7eb', marginVertical: 0, paddingHorizontal: 10 }}
433
272
  >
434
- <HStack space={'md'} className="flex-1 w-[100%] py-3 items-center">
273
+ {/* Optimized subscription handler */}
274
+ <SubscriptionHandler
275
+ subscribeToMore={subscribeToMore}
276
+ document={CHAT_MESSAGE_ADDED}
277
+ variables={{ channelId: channel?.id?.toString() }}
278
+ enabled={shouldQuery}
279
+ updateQuery={(prev, { subscriptionData }) => {
280
+ if (!subscriptionData?.data?.chatMessageAdded || !isMountedRef.current) return prev;
281
+
282
+ const newMessage = subscriptionData.data.chatMessageAdded;
283
+
284
+ // Optimized cache update - check for duplicates more efficiently
285
+ const existingMessages = prev?.messages;
286
+ const messageExists = existingMessages?.data?.some((msg: any) => msg.id === newMessage.id);
287
+
288
+ if (!messageExists) {
289
+ return {
290
+ ...prev,
291
+ messages: {
292
+ ...existingMessages,
293
+ data: [...(existingMessages?.data || []), newMessage],
294
+ totalCount: (existingMessages?.totalCount || 0) + 1,
295
+ },
296
+ };
297
+ }
298
+ return prev;
299
+ }}
300
+ onError={(error) => {
301
+ console.error(`Subscription error for channel ${channel?.id}:`, error);
302
+ }}
303
+ />
304
+
305
+ <HStack space={'md'} className="flex-1 w-[100%] py-3 items-center">
435
306
  <Box className="flex-[0.1] items-start pl-3">
436
307
  <AvatarGroup>
437
308
  {channelMembers &&
@@ -481,42 +352,6 @@ export const DialogsListItemComponent: React.FC<IDialogListItemProps> = function
481
352
  key={`last-msg-${lastMessage?.id || 'none'}-${messages.length}`}
482
353
  title={displayTitle}
483
354
  lastMessage={lastMessage}
484
- channelId={channel?.id}
485
- subscribeToNewMessages={() =>
486
- shouldQuery
487
- ? subscribeToMore({
488
- document: CHAT_MESSAGE_ADDED,
489
- variables: {
490
- channelId: channel.id?.toString(),
491
- },
492
- updateQuery: (prev, { subscriptionData }: any) => {
493
- if (!subscriptionData.data) return prev;
494
- const newMessage: any = subscriptionData?.data?.chatMessageAdded;
495
- if (isMountedRef.current) {
496
- if (!messages.some((msg) => msg.id === newMessage.id)) {
497
- setMessages((prevMessages) => [...prevMessages, newMessage]);
498
- setLastMessage(newMessage);
499
- }
500
- }
501
- const existingMessages: any = prev?.messages;
502
- const previousData = existingMessages?.data
503
- ? [...existingMessages.data, newMessage]
504
- : [];
505
- const totalMsgCount = existingMessages?.totalCount + 1;
506
- const merged = {
507
- ...prev,
508
- messages: {
509
- ...existingMessages,
510
- data: previousData,
511
- totalCount: totalMsgCount,
512
- },
513
- };
514
- return merged;
515
- },
516
- })
517
- : undefined
518
- }
519
- subscribeToMore={subscribeToMore}
520
355
  />
521
356
  </Box>
522
357
  </HStack>