Socket.io with React and Node.js - 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);
});
}, [])

Related

How to correctly load data on WS event?

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

I want to use socket.emit() outside io.on("connection"), is this approach correct?

I'm making a tweet deleter, and I want to update the user on the progress.
I'm new to socket.io, I managed to connect the react frontend to the nodejs/express backend.
io.on("connection", (socket) => {
console.log("new connection");
socket.on("disconnect", () => console.log("disconnected"));
});
When a user clicks the delete button, a delete request goes to the backend, the file containing the tweets is then processed and thanks to Bull, each tweet is queued as a job.
because I added ìo to my routes, I can use it inside of them, but io.emit() emits to connected clients, and I only want emit to sender by using socket.emit() inside my routes as well as inside my jobs in the queue.
The approach I tried was to write a function inside io.on("connection") like this and to make it global :
io.on("connection", (socket) => {
console.log("new connection");
socket.on("disconnect", () => console.log("disconnected"));
global.emitCustom = function (event, payload) {
socket.emit(event, payload);
};
});
which allowed me to use in the queue process function :
const deletionProcess = async ({
data: { tweetId, tokens, twitterId, numberOfTweets, index },
}) => {
emitCustom("deleting", {
type: "deleting",
progress: Math.round(((index + 1) / numberOfTweets) * 100),
});
};
Is there a better way to do this? is my approach wrong or does it present any flaws?
It worked in the few tests I did.

How to discard incoming event in Socket.io-client

Hi I am trying to make a tracker app and I'm using socket.io for both server and client. On my client app, I want to disregard message event whenever my browser is not on focus. my code is like this for the client app :
const socket = io('http://localhost:4000');
const [browserState, setBrowserState] = useState('');
useEffect(() => {
socket.on('connect', () => {
console.log('connected');
socket.on("message", payload => {
//payload need to be passed to Map component
console.log(payload);
});
});
},[]);
useEffect(() => {
document.onvisibilitychange = function(){
setBrowserState(document.visibilityState)
}
if(browserState === 'hidden') socket.volatile.emit("message", payload => payload)
},[browserState]);
and on my server is just simply:
io.on('connection', socket => {
socket.on('message', (payload)=>{
console.log(payload)
io.emit('message', payload)
});
The problem is on the client-side for the code socket.volatile.emit("message", payload => payload). if I use socket.volatile.on it's working. but I still receive message event on the client. if I use socket.volatile.emit the server is crashing.
Additional Question: is it okay if my client side io.protocol = 5 and my server is io.protocol = 4?
I'm really new to this. Please advise. :) Thanks!
It can be discarded easily by not replying to incoming sockets
io.on('connection', socket => {
socket.on('message', (payload)=>{
console.log(payload)
// removed this line io.emit('message', payload)
});

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