How to correctly load data on WS event? - node.js

So. Server sends event to every client in specific room on message submit for other clients to load messages, so they can see that new message. React client correctly receives, but loads messages in weird way. It renders page with no messages and after few miliseconds loads correctly. It cause very ugly blink.
Client socket.io code:
useEffect(() => {
loadRooms(userId).then(data => {
dispatch(setRoomsList(data))
})
loadMessages(roomId).then(data => {
dispatch(setMessageList(data))
});
socket.on('receive_message', data => {
dispatch(addNewMessage({id: data.id, content: data.message, roomId: data.room, userName: data.userName}));
console.log(messageList)
})
}, [dispatch, roomId, socket])
Client who sends message doesn't have this bug. On this client, axios request works correctly.
Submit message handler:
const handleMessageSubmit = e => {
e.preventDefault()
sendMessage(userName, currentMessage, roomId).then(data => {
loadMessages(roomId).then(data => {
dispatch(setMessageList(data))
socket.emit('send_message', {message: currentMessage, room: roomId});
})
setCurrentMessage('')
})
}
Loading message normally with that handler work just fine, but with socket.io it is bugged.
How messages are displayed in page:
{messageList.map(message =>
<div sx={{overflow: 'auto'}} key={message.id} style={{display: 'block', width: '70vw', marginTop: '5px', clear: 'both'}}>
<div style={{wordWrap: 'break-word'}} key={message.id}>{message.userName}: {message.content}</div>
</div>
)}
messageList is redux state, which I get using mapStateToProps
Socket.io on the server side:
io.on('connection', (socket) => {
let prevRoom = 1;
console.log("Users's id", socket.id)
socket.on('send_message', (data) => {
socket.to(data.room).emit('receive_message', data.message)
console.log("sent", data.message)
})
socket.on('join_room', (data) => {
socket.leave(prevRoom)
prevRoom = data;
socket.join(data)
console.log("joined")
})
} )

The idea behind sending data through web sockets is that you read it from message that you receive and then render. You don't do that here. You ignore the data in "receive_message" and call "loadMessages" that, I assume, loads full messages list from api. This extra call to api causes the ugly blink, but the code that resets messageList is not in question (probably in redux).
You need to get rid of extra api call and append the new message received via web socket to your existing message list

Related

I am loosing chat messages when i refresh the page with socketio but they are saved in DB

I created private chat app using Socketio with nodejs server. i am saving chat messages to monogdb and sending back tom client but when i refresh page chat messages are disappearing but they are save d to db. Can anyone help me please?
CLient
useEffect(() => {
socket.on("connect", () => {
console.log("Connection established!");
});
socket.on("loggedin", () => {
console.log("you're logged in!");
setLoggedIn(true);
fetchOnlineUsers();
socket.on("newConnection", () => {
console.log("watch out! a new challenger appears!");
fetchOnlineUsers();
});
});
socket.on("message", (newMessage) => {
console.log("a new message appeared!", newMessage);
setChatHistory((chatHistory) => [...chatHistory, newMessage]);
});
}, []);
SERVER
socket.on("sendmessage", async({ message, room }) => {
const newMessage = new MsgModel({
text:message.text,
sender: message.sender,
socketId:message.socketId
});
newMessage.save().then(() => {
socket.to(room).emit('message', newMessage)
})
console.log("Message",newMessage);
// Emits only to people inside of the defined "room"
})
From what you've shared, I can only understand that you are sending messages to the receiver through socket, but when the user connects again to that particular room, are you fetching all previous messages from your mongo conversation document?
In order to receive messages that were sent previously, you need to fetch all messages associated to that room when the user first loads the page. You can create another API that fetches the records from where you previously stored your messages and list it for the user like you do when receiving the message socket trigger

SocketIO & ReactJs: Message is getting emitted multiple times from server to frontend

I am making a chatroom application using reactjs(socketIo) and nodeJs. When I emit the message from frontend to server, it gets emitted only once, but when I emit it back from server to frontend, It gets emitted multiple times.
Frontend Code:
function handleChat(event) {
event.preventDefault();
socket.emit("newMessage", { message: msg, user: props.user.name });
setMsg("");
}
socket.on("messageList", (list) => {
console.log(list.message);
var allMsg = messages
allMsg.push({message: list.message.message, name: list.message.user})
setMessages(allMsg);
})
In the code above newMessage is the handler for emitting the message from frontend to server and messageList is the handler for message from the server(which is the issue).
Backend Code:
namespace.forEach((ns) => {
io.of(`/${ns.name}`).on("connect", (socket1) => {
// console.log(ns.name, socket1.id);
socket1.on("name",(name)=>{
console.log(name);
})
socket1.on("newMessage", (message) => {
console.log(message);
io.of(`/${ns.name}`).emit("messageList",{message})
});
});
});

Socket.io with React and Node.js

I'm kinda new using sockets.io and I have a pretty basic question.
I have this code on the server side where I basically try to listen for the event called setName and I pass the username parameter and I emit a new event called "setNameCb" to the client side.
io.on("connection", (socket) => {
socket.on("setName", (username) => {
socket.emit("setNameCb", username);
});
});
And this code on the client side where I try to pass an username when the button is clicked. After that I listen for the setNameCb event from the server side and I try to get the username value.
const setNameFunction = () => {
socket.emit("setName", "Cristian");
};
socket.on("setNameCb", (username) => {
alert(username);
});
return (
<div className="App">
<button onClick={setNameFunction}>set name</button>
</div>
);
Alright, eveything is working, but the problem is that I get multiple alerts with the username and I just can't figure it out why that is happening.
I'd really appreciate an explanation for this one. I know it's a very basic question but that will help me to understand better the concept.
Thank you a lot!
The socket listener in react component is created on every render, if not wrapped into useEffect.
useEffect(() => {
const setNameFunction = () => {
socket.emit("setName", "Cristian");
};
socket.on("setNameCb", (username) => {
alert(username);
});
}, [])

Socket.io: Other client only updates when being interacted

I'm trying to set up a realtime application using socket.io in Angular and node.js, which is not working as intended.
Whenever a client is making a new post, the other clients won't update until you interact with the client (e.g. clicking somewhere on the page, or clicking on the browsers tab).
However, having console open in the browser, I can see the new post in the console when I log the posts/objects - without the need to interact with the clients.
Angular:
import io from 'socket.io-client';
const socket = io('http://localhost:3000');
posts: Post[] = [];
...
// Inside ngOnInit:
socket.on('data123', (res) => {
console.log('Updating list..', res);
this.postService.getPosts();
this.postsSub = this.postService.getPostUpdateListener()
.subscribe((posts: Post[]) => {
this.posts = posts;
});
});
Displaying in the template:
<... *ngFor="let item of posts">
Inside PostsService:
getPosts() {
this.http.get<{ message: string, posts: Post[] }>('http://localhost:3000/api/posts')
.subscribe((postData) => {
this.posts = postData.posts;
this.postsUpdate.next([...this.posts]);
});
}
Node.js - this socket.io solution is not yet sending the actual list:
const io = socket(server);
io.sockets.on('connection', (socket) => {
console.log(`new connection id: ${socket.id}`);
sendData(socket);
})
function sendData(socket){
socket.emit('data123', 'TODO: send the actual updated list');
setTimeout(() => {
console.log('sending to client');
sendData(socket);
}, 3000);
}
What worked as intended:
Using setInterval instead "socket.on(..)" on the front-end gave the intended result, meaning the clients will update automatically without the need of interacting. I'm fully aware this solution is horrible, but I assume this pinpointing that it's something wrong with socket solution above in Angular part.
wait, every time when socket.on('data123', (res) => {... you are creating new subscribe? it's wrong way...you must create subscribe in your socket connect feature

Using socketio with react redux

Im building a small chat application using react,redux,socketio and node with mongoose. Normally redux flows through actions (which makes API calls and receive data) and dispatch the data. But in my case the socket will emit to a certain event but it would not return data until we manually emit the data from the back-end. so to achieve the proper redux flow should i add a socket event on actions to retrieve the data (coming from back-end) and then dispatch it or is there any other proper way to achieve this?
Here is a sample code of what i'm planing to do in
Actions file
function sendMessage(data) {
return {
type: SEND_MESSAGE,
payload: data
};
}
export const sendNewMessage = (socket,data) => {
return dispatch => {
socket.emit("send message",data);
socket.on("new message",function(data){
dispatch(sendMessage(data));
});
};
};
That seems perfectly reasonable to me. I would suggest using thunk's "extra argument" for this such that your components do not need to know about the actual socket object:
const store = createStore(
reducer,
applyMiddleware(thunk.withExtraArgument({ socket }))
)
export const sendNewMessage = (data) =>
(dispatch, getState, { socket }) => {
socket.emit("send message", data)
socket.on("new message", (data) => {
dispatch(sendMessage(data))
})
}

Resources