Skip to content

Commit 1cef9d2

Browse files
fixes for thread recovery
1 parent 6c621fd commit 1cef9d2

File tree

5 files changed

+90
-32
lines changed

5 files changed

+90
-32
lines changed

src/components/Channel/Channel.tsx

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1508,24 +1508,63 @@ const ChannelWithContext = <
15081508
Re,
15091509
Us
15101510
>['loadMoreThread'] = async () => {
1511-
if (threadLoadingMore || !thread?.id) return;
1511+
if (threadLoadingMore || !thread?.id) {
1512+
return;
1513+
}
15121514
setThreadLoadingMore(true);
15131515

1514-
if (channel) {
1515-
const parentID = thread.id;
1516+
try {
1517+
if (channel) {
1518+
const parentID = thread.id;
1519+
1520+
/**
1521+
* In the channel is re-initializing, then threads may get wiped out during the process
1522+
* (check `addMessagesSorted` method on channel.state). In those cases, we still want to
1523+
* preserve the messages on active thread, so lets simply copy messages from UI state to
1524+
* `channel.state`.
1525+
*/
1526+
channel.state.threads[parentID] = threadMessages;
1527+
const oldestMessageID = threadMessages?.[0]?.id;
1528+
1529+
const limit = 50;
1530+
const queryResponse = await channel.getReplies(parentID, {
1531+
id_lt: oldestMessageID,
1532+
limit,
1533+
});
1534+
1535+
const updatedHasMore = queryResponse.messages.length === limit;
1536+
const updatedThreadMessages = channel.state.threads[parentID] || [];
1537+
loadMoreThreadFinished(updatedHasMore, updatedThreadMessages);
1538+
}
1539+
} catch (err) {
1540+
console.warn('Message pagination request failed with error', err);
1541+
setError(err);
1542+
setThreadLoadingMore(false);
1543+
throw err;
1544+
}
1545+
};
15161546

1517-
const oldMessages = channel.state.threads[parentID] || [];
1518-
const oldestMessageID = oldMessages?.[0]?.id;
1547+
const reloadThread = async () => {
1548+
if (!channel || !thread?.id) return;
1549+
1550+
setThreadLoadingMore(true);
1551+
try {
1552+
const parentID = thread.id;
15191553

15201554
const limit = 50;
1555+
channel.state.threads[parentID] = [];
15211556
const queryResponse = await channel.getReplies(parentID, {
1522-
id_lt: oldestMessageID,
1523-
limit,
1557+
limit: 50,
15241558
});
15251559

15261560
const updatedHasMore = queryResponse.messages.length === limit;
15271561
const updatedThreadMessages = channel.state.threads[parentID] || [];
15281562
loadMoreThreadFinished(updatedHasMore, updatedThreadMessages);
1563+
} catch (err) {
1564+
console.warn('Thread loading request failed with error', err);
1565+
setError(err);
1566+
setThreadLoadingMore(false);
1567+
throw err;
15291568
}
15301569
};
15311570

@@ -1707,6 +1746,7 @@ const ChannelWithContext = <
17071746
closeThread,
17081747
loadMoreThread,
17091748
openThread,
1749+
reloadThread,
17101750
setThreadLoadingMore,
17111751
thread,
17121752
threadHasMore,

src/components/Channel/hooks/useCreateThreadContext.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export const useCreateThreadContext = <
2525
closeThread,
2626
loadMoreThread,
2727
openThread,
28+
reloadThread,
2829
setThreadLoadingMore,
2930
thread,
3031
threadHasMore,
@@ -50,6 +51,7 @@ export const useCreateThreadContext = <
5051
closeThread,
5152
loadMoreThread,
5253
openThread,
54+
reloadThread,
5355
setThreadLoadingMore,
5456
thread,
5557
threadHasMore,
@@ -58,6 +60,7 @@ export const useCreateThreadContext = <
5860
}),
5961
[
6062
allowThreadMessagesInChannel,
63+
loadMoreThread,
6164
threadHasMore,
6265
threadId,
6366
threadLoadingMore,

src/components/MessageList/MessageList.tsx

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,7 @@ import {
6868
useTranslationContext,
6969
} from '../../contexts/translationContext/TranslationContext';
7070

71-
import type {
72-
FormatMessageResponse,
73-
Channel as StreamChannel,
74-
} from 'stream-chat';
71+
import type { Channel as StreamChannel } from 'stream-chat';
7572

7673
import type {
7774
DefaultAttachmentType,
@@ -358,9 +355,9 @@ const MessageListWithContext = <
358355
* So these values only get used if `initialScrollToFirstUnreadMessage` prop is true.
359356
*/
360357
const topMessageBeforeUpdate = useRef<
361-
FormatMessageResponse<At, Ch, Co, Me, Re, Us>
358+
MessageType<At, Ch, Co, Ev, Me, Re, Us>
362359
>();
363-
const topMessageAfterUpdate = channel?.state.messages[0];
360+
const topMessageAfterUpdate = messageList[messageList.length - 1];
364361

365362
const [autoscrollToTop, setAutoscrollToTop] = useState(false);
366363

@@ -519,8 +516,8 @@ const MessageListWithContext = <
519516
if (
520517
(hasNewMessage && isMyMessage) ||
521518
messageListLengthAfterUpdate < messageListLengthBeforeUpdate.current ||
522-
(topMessageBeforeUpdate.current &&
523-
topMessageAfterUpdate &&
519+
(topMessageBeforeUpdate.current?.created_at &&
520+
topMessageAfterUpdate?.created_at &&
524521
topMessageBeforeUpdate.current.created_at <
525522
topMessageAfterUpdate.created_at)
526523
) {
@@ -544,11 +541,10 @@ const MessageListWithContext = <
544541
}
545542
};
546543

547-
// If channel is not upto date, then always display scrollToBottom button.
548-
if (!channel?.state.isUpToDate && !scrollToBottomButtonVisible) {
549-
setScrollToBottomButtonVisible(true);
550-
} else if (channel?.state.isUpToDate) {
544+
if (threadList || channel?.state.isUpToDate) {
551545
scrollToBottomIfNeeded();
546+
} else if (!scrollToBottomButtonVisible) {
547+
setScrollToBottomButtonVisible(true);
552548
}
553549

554550
/**
@@ -1006,7 +1002,7 @@ const MessageListWithContext = <
10061002
<ScrollToBottomButton
10071003
onPress={goToNewMessages}
10081004
showNotification={scrollToBottomButtonVisible}
1009-
unreadCount={channel?.countUnread()}
1005+
unreadCount={threadList ? 0 : channel?.countUnread()}
10101006
/>
10111007
</>
10121008
)}

src/components/Thread/Thread.tsx

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import {
66
MessageInput as DefaultMessageInput,
77
MessageInputProps,
88
} from '../MessageInput/MessageInput';
9-
9+
import {
10+
ChatContextValue,
11+
useChatContext,
12+
} from '../../contexts/chatContext/ChatContext';
1013
import {
1114
MessagesContextValue,
1215
useMessagesContext,
@@ -37,10 +40,11 @@ type ThreadPropsWithContext<
3740
Me extends UnknownType = DefaultMessageType,
3841
Re extends UnknownType = DefaultReactionType,
3942
Us extends UnknownType = DefaultUserType
40-
> = Pick<MessagesContextValue<At, Ch, Co, Ev, Me, Re, Us>, 'MessageList'> &
43+
> = Pick<ChatContextValue<At, Ch, Co, Ev, Me, Re, Us>, 'client'> &
44+
Pick<MessagesContextValue<At, Ch, Co, Ev, Me, Re, Us>, 'MessageList'> &
4145
Pick<
4246
ThreadContextValue<At, Ch, Co, Ev, Me, Re, Us>,
43-
'closeThread' | 'loadMoreThread' | 'thread'
47+
'closeThread' | 'loadMoreThread' | 'reloadThread' | 'thread'
4448
> & {
4549
/**
4650
* Additional props for underlying MessageInput component.
@@ -90,10 +94,12 @@ const ThreadWithContext = <
9094
additionalMessageInputProps,
9195
additionalMessageListProps,
9296
autoFocus = true,
97+
client,
9398
closeThread,
9499
closeThreadOnDismount = true,
95100
disabled,
96101
loadMoreThread,
102+
reloadThread,
97103
MessageInput = DefaultMessageInput,
98104
MessageList,
99105
onThreadDismount,
@@ -108,6 +114,18 @@ const ThreadWithContext = <
108114
if (thread?.id && thread.reply_count) {
109115
loadMoreThreadAsync();
110116
}
117+
118+
const recoveredHandler = client.on('connection.recovered', reloadThread);
119+
const changedHandler = client.on('connection.changed', (event) => {
120+
if (event.online) {
121+
reloadThread();
122+
}
123+
});
124+
125+
return () => {
126+
recoveredHandler.unsubscribe();
127+
changedHandler.unsubscribe();
128+
};
111129
}, []);
112130

113131
useEffect(
@@ -170,23 +188,23 @@ export const Thread = <
170188
>(
171189
props: ThreadProps<At, Ch, Co, Ev, Me, Re, Us>,
172190
) => {
191+
const { client } = useChatContext<At, Ch, Co, Ev, Me, Re, Us>();
173192
const { MessageList } = useMessagesContext<At, Ch, Co, Ev, Me, Re, Us>();
174-
const { closeThread, loadMoreThread, thread } = useThreadContext<
175-
At,
176-
Ch,
177-
Co,
178-
Ev,
179-
Me,
180-
Re,
181-
Us
182-
>();
193+
const {
194+
closeThread,
195+
loadMoreThread,
196+
reloadThread,
197+
thread,
198+
} = useThreadContext<At, Ch, Co, Ev, Me, Re, Us>();
183199

184200
return (
185201
<ThreadWithContext<At, Ch, Co, Ev, Me, Re, Us>
186202
{...{
203+
client,
187204
closeThread,
188205
loadMoreThread,
189206
MessageList,
207+
reloadThread,
190208
thread,
191209
}}
192210
{...props}

src/contexts/threadContext/ThreadContext.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export type ThreadContextValue<
2929
closeThread: () => void;
3030
loadMoreThread: () => Promise<void>;
3131
openThread: (message: MessageType<At, Ch, Co, Ev, Me, Re, Us>) => void;
32+
reloadThread: () => void;
3233
setThreadLoadingMore: React.Dispatch<React.SetStateAction<boolean>>;
3334
thread: MessageType<At, Ch, Co, Ev, Me, Re, Us> | null;
3435
threadHasMore: boolean;

0 commit comments

Comments
 (0)