The following code is part of a chat application build with react native which is working via a socket.io server.
const Message = ({text, isSent}) => {
return (
<View style={[styles.messageContainer, isSent ? styles.sentMessage : styles.receivedMessage]}>
<Text style={styles.messageText}>{text}</Text>
</View>
)
}
export const ChatScreen = ({ route }) => {
const {control, handleSubmit, reset} = useForm();
const socket = useContext(SocketContext)
const { user } = route.params;
const onSendPress = async(data) => {
const message = data.messageInput
const uuid = await AsyncStorage.getItem('uuid')
if(message === '') return
console.log('Message Send: ' message ' | to: ' user.uuid)
addMessage(message, true);
reset();
socket.emit('send-chat-message', {
message: message,
sender: uuid,
recipient: user.uuid
})
}
socket.on('receive-chat-message', async(data) => {
console.log(data)
if(data.sender !== user.uuid) return
addMessage(data.message, false)
})
const [messages, setMessages] = useState([
{ text: 'Hello! How can I help you today?', isSent: false },
{ text: 'Hi! I was wondering if you could give me some advice.', isSent: true },
{ text: 'Of course! I would be happy to help.', isSent: false },
]);
const scrollViewRef = useRef(null);
const addMessage = (text, isSent) => {
setMessages([...messages, { text, isSent }]);
scrollViewRef.current.scrollToEnd({ animated: true });
};
return (
<View style={styles.background}>
<StatusBar style="auto" />
<ScrollView style={styles.container} ref={scrollViewRef}>
{messages.map((message, index) => (
<Message key={index} text={message.text} isSent={message.isSent} />
))}
</ScrollView>
<KeyboardAvoidingView behavior='padding' keyboardVerticalOffset={55} style={styles.bottomTextInput}>
<Controller
name={'messageInput'}
control={control}
render={({field: {value = '', onChange, onBlur}}) => (
<>
<View style={styles.input}>
<TextInput
style={{paddingHorizontal: 11, paddingTop: 2}}
onChangeText={onChange}
onBlur={onBlur}
value={value}
multiline={true}
fontSize={17}
/>
</View>
<TouchableOpacity onPress={handleSubmit(onSendPress)} style={[styles.sendInput, { backgroundColor: value === '' ? '#cacaca' : '#858AE3'}]}>
<Ionicons
name={'paper-plane'}
size={20}
color={'white'} />
</TouchableOpacity>
</>
)}
/>
</KeyboardAvoidingView>
</View>
)
}
Now everything is working quite well, the only problem I'm running into is that somehow the data I receive with the receive-chat-message
event is doubling each time a new message comes in. To make it visual, this is my console after three different messages:
{"message": "First Message", "recipient": "7fa68f52-ad00-4bdd-9cfc-2af2e15d8d8d", "sender": "2bfe33f0-4882-4ff6-a9ac-4356cd3e7a66"}
{"message": "Second Message", "recipient": "7fa68f52-ad00-4bdd-9cfc-2af2e15d8d8d", "sender": "2bfe33f0-4882-4ff6-a9ac-4356cd3e7a66"}
{"message": "Second Message", "recipient": "7fa68f52-ad00-4bdd-9cfc-2af2e15d8d8d", "sender": "2bfe33f0-4882-4ff6-a9ac-4356cd3e7a66"}
{"message": "Third Message", "recipient": "7fa68f52-ad00-4bdd-9cfc-2af2e15d8d8d", "sender": "2bfe33f0-4882-4ff6-a9ac-4356cd3e7a66"}
{"message": "Third Message", "recipient": "7fa68f52-ad00-4bdd-9cfc-2af2e15d8d8d", "sender": "2bfe33f0-4882-4ff6-a9ac-4356cd3e7a66"}
{"message": "Third Message", "recipient": "7fa68f52-ad00-4bdd-9cfc-2af2e15d8d8d", "sender": "2bfe33f0-4882-4ff6-a9ac-4356cd3e7a66"}
{"message": "Third Message", "recipient": "7fa68f52-ad00-4bdd-9cfc-2af2e15d8d8d", "sender": "2bfe33f0-4882-4ff6-a9ac-4356cd3e7a66"}
I checked if the server is really just getting one request at a time and it is like this. The server is only getting one request and is also only emitting one request to the client who is receiving the message.
I appreciate any help since the problem is literally bugging me out.
CodePudding user response:
Most likely error is due to you are calling socket.on('receive-chat-message', ...)
directly in component body, that means that every time component is rerendered - this line is executed, so you have multiple listeners in same component executing same code on this event, as you can see - every time you call setMessages
- component is rerendered, additional listener is added and you are getting 1 more duplicate.
Solution: Wrap it with useEffect
:
useEffect(() => {
if (!user?.uuid) return;
const fn = async (data) => {
console.log(data);
if (data.sender !== user.uuid) return;
addMessage(data.message, false);
};
socket.on("receive-chat-message", fn);
return () => {
socket.off("receive-chat-message", fn);
};
}, [socket, user?.uuid]);
Possible error #2 - setMessages([...messages, { text, isSent }]);
. Here you can get errors due to closures, prefered to use functional state setter:
setMessages(messages => [...messages, { text, isSent }]);