Skip to content

Commit 359c192

Browse files
authored
Merge pull request #624 from GetStream/fix-resend-functionality
CRNS-296: Fix resend functionality
2 parents 4dd8cd3 + 1dabfd4 commit 359c192

File tree

4 files changed

+267
-41
lines changed

4 files changed

+267
-41
lines changed
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
import React from 'react';
2+
import { Alert } from 'react-native';
3+
import { TouchableOpacity } from 'react-native-gesture-handler';
4+
import { useNavigation } from '@react-navigation/core';
5+
6+
import {
7+
DefaultAttachmentType,
8+
DefaultChannelType,
9+
DefaultCommandType,
10+
DefaultEventType,
11+
DefaultMessageType,
12+
DefaultReactionType,
13+
DefaultUserType,
14+
MessageInputContextValue,
15+
Search,
16+
SendRight,
17+
SendUp,
18+
UnknownType,
19+
useChannelContext,
20+
useMessageInputContext,
21+
useTheme,
22+
} from 'stream-chat-react-native';
23+
24+
import { NewDirectMessagingScreenNavigationProp } from '../screens/NewDirectMessagingScreen';
25+
26+
import {
27+
LocalAttachmentType,
28+
LocalChannelType,
29+
LocalCommandType,
30+
LocalEventType,
31+
LocalMessageType,
32+
LocalReactionType,
33+
LocalUserType,
34+
} from '../types';
35+
36+
type NewDirectMessagingSendButtonPropsWithContext<
37+
At extends UnknownType = DefaultAttachmentType,
38+
Ch extends UnknownType = DefaultChannelType,
39+
Co extends string = DefaultCommandType,
40+
Ev extends UnknownType = DefaultEventType,
41+
Me extends UnknownType = DefaultMessageType,
42+
Re extends UnknownType = DefaultReactionType,
43+
Us extends UnknownType = DefaultUserType
44+
> = Pick<
45+
MessageInputContextValue<At, Ch, Co, Ev, Me, Re, Us>,
46+
'giphyActive' | 'sendMessage'
47+
> & {
48+
/** Disables the button */ disabled: boolean;
49+
};
50+
51+
const SendButtonWithContext = <
52+
At extends UnknownType = DefaultAttachmentType,
53+
Ch extends UnknownType = DefaultChannelType,
54+
Co extends string = DefaultCommandType,
55+
Ev extends UnknownType = DefaultEventType,
56+
Me extends UnknownType = DefaultMessageType,
57+
Re extends UnknownType = DefaultReactionType,
58+
Us extends UnknownType = DefaultUserType
59+
>(
60+
props: NewDirectMessagingSendButtonPropsWithContext<
61+
At,
62+
Ch,
63+
Co,
64+
Ev,
65+
Me,
66+
Re,
67+
Us
68+
>,
69+
) => {
70+
const { disabled = false, giphyActive, sendMessage } = props;
71+
const {
72+
theme: {
73+
colors: { accent_blue, grey_gainsboro },
74+
messageInput: { sendButton },
75+
},
76+
} = useTheme();
77+
78+
return (
79+
<TouchableOpacity
80+
disabled={disabled}
81+
onPress={sendMessage}
82+
style={[sendButton]}
83+
testID='send-button'
84+
>
85+
{giphyActive && <Search pathFill={accent_blue} />}
86+
{!giphyActive && disabled && <SendRight pathFill={grey_gainsboro} />}
87+
{!giphyActive && !disabled && <SendUp pathFill={accent_blue} />}
88+
</TouchableOpacity>
89+
);
90+
};
91+
92+
const areEqual = <
93+
At extends UnknownType = DefaultAttachmentType,
94+
Ch extends UnknownType = DefaultChannelType,
95+
Co extends string = DefaultCommandType,
96+
Ev extends UnknownType = DefaultEventType,
97+
Me extends UnknownType = DefaultMessageType,
98+
Re extends UnknownType = DefaultReactionType,
99+
Us extends UnknownType = DefaultUserType
100+
>(
101+
prevProps: NewDirectMessagingSendButtonPropsWithContext<
102+
At,
103+
Ch,
104+
Co,
105+
Ev,
106+
Me,
107+
Re,
108+
Us
109+
>,
110+
nextProps: NewDirectMessagingSendButtonPropsWithContext<
111+
At,
112+
Ch,
113+
Co,
114+
Ev,
115+
Me,
116+
Re,
117+
Us
118+
>,
119+
) => {
120+
const {
121+
disabled: prevDisabled,
122+
giphyActive: prevGiphyActive,
123+
sendMessage: prevSendMessage,
124+
} = prevProps;
125+
const {
126+
disabled: nextDisabled,
127+
giphyActive: nextGiphyActive,
128+
sendMessage: nextSendMessage,
129+
} = nextProps;
130+
131+
const disabledEqual = prevDisabled === nextDisabled;
132+
if (!disabledEqual) return false;
133+
134+
const giphyActiveEqual = prevGiphyActive === nextGiphyActive;
135+
if (!giphyActiveEqual) return false;
136+
137+
const sendMessageEqual = prevSendMessage === nextSendMessage;
138+
if (!sendMessageEqual) return false;
139+
140+
return true;
141+
};
142+
143+
const MemoizedNewDirectMessagingSendButton = React.memo(
144+
SendButtonWithContext,
145+
areEqual,
146+
) as typeof SendButtonWithContext;
147+
148+
export type SendButtonProps<
149+
At extends UnknownType = DefaultAttachmentType,
150+
Ch extends UnknownType = DefaultChannelType,
151+
Co extends string = DefaultCommandType,
152+
Ev extends UnknownType = DefaultEventType,
153+
Me extends UnknownType = DefaultMessageType,
154+
Re extends UnknownType = DefaultReactionType,
155+
Us extends UnknownType = DefaultUserType
156+
> = Partial<
157+
NewDirectMessagingSendButtonPropsWithContext<At, Ch, Co, Ev, Me, Re, Us>
158+
>;
159+
160+
/**
161+
* UI Component for send button in MessageInput component.
162+
*/
163+
export const NewDirectMessagingSendButton = (
164+
props: SendButtonProps<
165+
LocalAttachmentType,
166+
LocalChannelType,
167+
LocalCommandType,
168+
LocalEventType,
169+
LocalMessageType,
170+
LocalReactionType,
171+
LocalUserType
172+
>,
173+
) => {
174+
const navigation = useNavigation<NewDirectMessagingScreenNavigationProp>();
175+
const { channel } = useChannelContext<
176+
LocalAttachmentType,
177+
LocalChannelType,
178+
LocalCommandType,
179+
LocalEventType,
180+
LocalMessageType,
181+
LocalReactionType,
182+
LocalUserType
183+
>();
184+
185+
const { giphyActive, text } = useMessageInputContext<
186+
LocalAttachmentType,
187+
LocalChannelType,
188+
LocalCommandType,
189+
LocalEventType,
190+
LocalMessageType,
191+
LocalReactionType,
192+
LocalUserType
193+
>();
194+
195+
const sendMessage = async () => {
196+
if (!channel) return;
197+
channel.initialized = false;
198+
await channel.query({});
199+
try {
200+
await channel.sendMessage({ text });
201+
navigation.replace('ChannelScreen', {
202+
channelId: channel.id,
203+
});
204+
} catch (e) {
205+
Alert.alert('Error sending a message');
206+
}
207+
};
208+
209+
return (
210+
<MemoizedNewDirectMessagingSendButton<
211+
LocalAttachmentType,
212+
LocalChannelType,
213+
LocalCommandType,
214+
LocalEventType,
215+
LocalMessageType,
216+
LocalReactionType,
217+
LocalUserType
218+
>
219+
{...{ giphyActive, sendMessage }}
220+
{...props}
221+
{...{ disabled: props.disabled || false }}
222+
/>
223+
);
224+
};

examples/SampleApp/src/screens/NewDirectMessagingScreen.tsx

Lines changed: 11 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import React, { useContext, useEffect, useRef, useState } from 'react';
1+
import React, {
2+
useCallback,
3+
useContext,
4+
useEffect,
5+
useRef,
6+
useState,
7+
} from 'react';
28
import {
39
Alert,
410
Platform,
@@ -17,6 +23,8 @@ import {
1723
User,
1824
UserAdd,
1925
useTheme,
26+
SendButton,
27+
SendButtonProps,
2028
} from 'stream-chat-react-native';
2129

2230
import { RoundButton } from '../components/RoundButton';
@@ -29,6 +37,7 @@ import { useUserSearchContext } from '../context/UserSearchContext';
2937
import type { StackNavigationProp } from '@react-navigation/stack';
3038
import type { Channel as StreamChatChannel } from 'stream-chat';
3139

40+
import { NewDirectMessagingSendButton } from '../components/NewDirectMessagingSendButton';
3241
import type {
3342
LocalAttachmentType,
3443
LocalChannelType,
@@ -216,33 +225,6 @@ export const NewDirectMessagingScreen: React.FC<NewDirectMessagingScreenProps> =
216225
initChannel();
217226
}, [selectedUsersLength]);
218227

219-
/**
220-
* 1. If the current channel is draft, then we create the channel and then send message
221-
* Otherwise we simply send the message.
222-
*
223-
* 2. And then navigate to ChannelScreen
224-
*/
225-
const customSendMessage = async () => {
226-
if (!currentChannel?.current) return;
227-
228-
if (isDraft.current) {
229-
currentChannel.current.initialized = false;
230-
await currentChannel.current.query({});
231-
}
232-
233-
try {
234-
await currentChannel.current.sendMessage({
235-
text: messageInputText,
236-
});
237-
238-
navigation.replace('ChannelScreen', {
239-
channelId: currentChannel.current.id,
240-
});
241-
} catch (e) {
242-
Alert.alert('Error sending a message');
243-
}
244-
};
245-
246228
const renderUserSearch = ({ inSafeArea }: { inSafeArea: boolean }) => (
247229
<View
248230
style={[
@@ -396,7 +378,7 @@ export const NewDirectMessagingScreen: React.FC<NewDirectMessagingScreenProps> =
396378
enforceUniqueReaction
397379
keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : -300}
398380
onChangeText={setMessageInputText}
399-
sendMessage={customSendMessage}
381+
SendButton={NewDirectMessagingSendButton}
400382
setInputRef={(ref) => (messageInputRef.current = ref)}
401383
>
402384
{renderUserSearch({ inSafeArea: true })}

src/components/Channel/Channel.tsx

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,6 +1110,22 @@ const ChannelWithContext = <
11101110
}
11111111
};
11121112

1113+
const replaceMessage = (
1114+
oldMessage: MessageResponse<At, Ch, Co, Me, Re, Us>,
1115+
newMessage: MessageResponse<At, Ch, Co, Me, Re, Us>,
1116+
) => {
1117+
if (channel) {
1118+
channel.state.removeMessage(oldMessage);
1119+
channel.state.addMessageSorted(newMessage, true);
1120+
if (thread && newMessage.parent_id) {
1121+
const threadMessages =
1122+
channel.state.threads[newMessage.parent_id] || [];
1123+
setThreadMessages(threadMessages);
1124+
}
1125+
setMessages(channel.state.messages);
1126+
}
1127+
};
1128+
11131129
const createMessagePreview = ({
11141130
attachments,
11151131
mentioned_users,
@@ -1163,6 +1179,7 @@ const ChannelWithContext = <
11631179

11641180
const sendMessageRequest = async (
11651181
message: MessageResponse<At, Ch, Co, Me, Re, Us>,
1182+
retrying?: boolean,
11661183
) => {
11671184
const {
11681185
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -1193,7 +1210,7 @@ const ChannelWithContext = <
11931210

11941211
const messageData = {
11951212
attachments,
1196-
id,
1213+
id: retrying ? undefined : id,
11971214
mentioned_users:
11981215
mentioned_users?.map((mentionedUser) => mentionedUser.id) || [],
11991216
parent_id,
@@ -1222,7 +1239,11 @@ const ChannelWithContext = <
12221239

12231240
if (messageResponse.message) {
12241241
messageResponse.message.status = 'received';
1225-
updateMessage(messageResponse.message);
1242+
if (retrying) {
1243+
replaceMessage(message, messageResponse.message);
1244+
} else {
1245+
updateMessage(messageResponse.message);
1246+
}
12261247
}
12271248
} catch (err) {
12281249
console.log(err);
@@ -1270,9 +1291,13 @@ const ChannelWithContext = <
12701291
Re,
12711292
Us
12721293
>['retrySendMessage'] = async (message) => {
1273-
message = { ...message, status: 'sending' };
1274-
updateMessage(message);
1275-
await sendMessageRequest(message);
1294+
const statusPendingMessage = {
1295+
...message,
1296+
status: 'sending',
1297+
};
1298+
1299+
updateMessage(statusPendingMessage);
1300+
await sendMessageRequest(statusPendingMessage, true);
12761301
};
12771302

12781303
// hard limit to prevent you from scrolling faster than 1 page per 2 seconds

src/components/Message/Message.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -667,13 +667,8 @@ const MessageWithContext = <
667667
}
668668
};
669669

670-
const handleResendMessage = () => {
671-
const messageWithoutReservedFields = removeReservedFields(message);
672-
673-
return retrySendMessage(
674-
messageWithoutReservedFields as MessageResponse<At, Ch, Co, Me, Re, Us>,
675-
);
676-
};
670+
const handleResendMessage = () =>
671+
retrySendMessage(message as MessageResponse<At, Ch, Co, Me, Re, Us>);
677672

678673
const handleReplyMessage = () => {
679674
setQuotedMessageState(message);

0 commit comments

Comments
 (0)