message sent but not received socket.on(...) for client - node.js

(Working) I can see the message was sent to the server just fine:
// function used in reactjs(client)
const sendMessage = () => {
const data = {
room: room,
content: {
author: userName,
message: message
}
}
Promise.resolve(socket.emit('send_message', data))
.then(() => setMessage(''));
}
useEffect(() => {
socket.on('receive_message', data => {
console.log(data);
});
}, []);
// function used in index.js(server)
io.on('connection', (socket) => {
console.log(socket.id);
socket.on('join_room', (roomName) => {
socket.join(roomName); // creates room with the name
console.log('User Joined Room:', roomName);
});
socket.on('send_message', (data) => {
console.log("data =", data);
socket.to(data.room).emit('receive_message', data.content);
});
socket.on('disconnect', () => {
console.log('USER DISCONNECTED')
});
});
(Problem) Can anyone tell me why receive_message is not showing up in the console.log?
Note: it will only show if I do something like add some comment to the react file and save it.(weird)
The Full Code of both files
index.js(server)
const express = require('express');
const morgan = require('morgan');
const { createServer } = require('http');
const { Server } = require('socket.io');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());
app.use(morgan('dev'));
const httpServer = createServer(app);
const io = new Server(httpServer, {
cors: true,
origins: ['http://localhost:3000']
});
io.on('connection', (socket) => {
console.log(socket.id);
socket.on('join_room', (roomName) => {
socket.join(roomName); // creates room with the name
console.log('User Joined Room:', roomName);
});
socket.on('send_message', (data) => {
console.log("data =", data);
socket.to(data.room).emit('receive_message', data.content);
});
socket.on('disconnect', () => {
console.log('USER DISCONNECTED')
});
});
httpServer.listen(3002, () => console.log(`Socket.io test running on port 3002`))
app.js(client)
import React, { useEffect, useState } from 'react';
import io from 'socket.io-client';
import './App.css';
let socket = io('localhost:3002/');
function App() {
// variables
const [loggedIn, setLoggedIn] = useState(false);
const [room, setRoom] = useState('');
const [userName, setUserName] = useState('');
const [message, setMessage] = useState('');
const [messageList, setMessageList] = useState([]);
// functions
const connectToRoom = () => {
console.log('button clicked');
setLoggedIn(true);
socket.emit('join_room', room); // join_room copied from backend
}
const sendMessage = () => {
const data = {
room: room,
content: {
author: userName,
message: message
}
}
Promise.resolve(socket.emit('send_message', data))
.then(() => setMessage(''));
}
// setup
useEffect(() => {
socket.on('receive_message', data => {
console.log(data);
});
}, []);
return (
<div className="App">
{!loggedIn ?
<div className="logIn">
<div className="inputs">
<input type="text" placeholder="Name..." value={userName} onChange={ev => setUserName(ev.target.value)} />
<input type="text" placeholder="Room..." value={room} onChange={ev => setRoom(ev.target.value)} />
</div>
<button onClick={connectToRoom}>Enter Chat</button>
</div> :
<div className="chatContainer">
<div className="messages">
<h1>{room}</h1>
{messageList.map((messageInfo, index) =>
<div key={index} className="messageBox">
<p>{messageInfo.author}: {messageInfo.message}</p>
</div>
)}
</div>
<div className="messageInputs">
<input type="text" placeholder="Message..." value={message} onChange={ev => setMessage(ev.target.value)} />
<button onClick={sendMessage}>Send</button>
</div>
</div>
}
</div>
);
}
export default App;

I found the issue. The state is managed in two different places on the client side.
For the socket.on('receive_message', ...) in useEffect, this will show up on every single other browser console logged into the current room except the current browser's console(the browser sending the message).
To also get it to show in current console browser window, I also needed to add the state in where I am submitting it.
const sendMessage = () => {
const data = {
room: room,
content: {
author: userName,
message: message
}
}
Promise.resolve(socket.emit('send_message', data))
.then(() => console.log(data)) // <------- add this line here
.then(() => setMessage(''));
}
This of course means setMessageList will need to be called both inside the sendMessage() and the socket.on('receive_message',...)(within the useEffect())
However, be careful when calling setMessageList inside socket.on('receive_message',...). The reasons for this is because it is called within a single useEffect one time, it captures the state from when that function was called and will not update. In other words, you cannot use setMessageList([...messageList, data]) because this function will only read messageList from when this function was first called initially.
To get around this, I see two options...
You can remove the [] inside the useEffect((...), []) forcing it to run in an infinite loop (this seems expensive).
The other option is to have the data on the backend update, save, then send back to which the setMessageList(data)(inside socket.on('receive_message',...)) will have to reload all the messages and re-render them all. (still seems expensive, but not as much as the first option)

Related

socket.io insufficient resources - react js and node

I'm experimenting a bit with socket.io to create a chat app in real time.
Everything seems to work fine but after a few second socket.io gives me this error:
"websocket.js:50 WebSocket connection to 'ws://localhost:5000/socket.io/?EIO=4&transport=websocket&sid=gbx90aAo73oDoP86AAVP' failed: Insufficient resources"
What am I doing wrong?
Client
import React from "react";
import { useState, useEffect } from "react";
import { io } from "socket.io-client";
const App = () => {
const [conv, setConv] = useState([]);
const [input, setInput] = useState({
sender: "user",
text: "",
});
const socket = io("http://localhost:5000");
const handleChange = (e) => {
setInput({
sender: "user",
text: e.target.value,
});
};
const handleSubmit = (e) => {
e.preventDefault();
socket.emit("input", input);
};
useEffect(() => {
socket.on("output", (data) => {
setConv(data[0].messages);
});
}, [conv]);
return (
<div className="App">
<form onSubmit={handleSubmit}>
<h1>chat</h1>
<div id="messages">
{conv.map((msg, index) => (
<div key={index}>
<p>
{msg.sender}: {msg.text}
</p>
</div>
))}
</div>
<input onChange={handleChange} type="text" />
<input type="submit" />
</form>
</div>
);
};
export default App;
Server
const express = require("express");
const socketIo = require("socket.io");
const http = require("http");
const PORT = 5000;
const app = express();
const server = http.createServer(app);
const io = socketIo(server, {
cors: {
origin: "http://localhost:3000",
},
}); //in case server and client run on different urls
const MongoClient = require("mongodb").MongoClient;
MongoClient.connect("mongodb://localhost:27017", (err, db) => {
if (err) throw err;
console.log("connected");
const database = db.db("local");
const chat = database.collection("chat");
//connect to socket .io
io.on("connection", (socket) => {
// get chat from collection
chat.find().toArray((err, res) => {
if (err) throw err;
// emit the messages
socket.emit("output", res);
});
//handle input events
socket.on("input", (input) => {
let sender = input.sender;
let receiver = "bot";
let text = input.text;
// check if inside chat collection the members array contains the sender and receiver
chat.find({ members: { $all: [sender, receiver] } }).toArray((err, res) => {
if (err) throw err;
let senderInDb = res[0].members[0];
let receiverInDb = res[0].members[1];
//if yes then push the message to the messages array
if (sender === senderInDb && receiver === receiverInDb) {
chat.updateOne(
{ members: { $all: [sender, receiver] } },
{ $push: { messages: { sender: sender, text: text } } }
);
}
});
});
});
});
server.listen(PORT, (err) => {
if (err) console.log(err);
console.log("Server running on Port ", PORT);
});

React js socket io emit

im building a chat app with mern and socket.io, i tried to emit with callback but it emit function not value and when i delete setList(l=>[...l,data]) it emit (first problem).
And the second problem, when i recieve the message and push it to list twice (useEffect).
Client side:
import React,{ useState,useEffect } from 'react';
import socketIOClient from "socket.io-client";
function Room(props) {
const [message,setMessage]=useState('');
const [list, setList] = useState([]);
const ENDPOINT = "http://localhost:5000";
const socket = socketIOClient(ENDPOINT, {
credentials: true
});
const sendMessage=(e)=>{
e.preventDefault();
let data=message;
//the first problem is here
socket.emit('send',(data)=>{
setList(l=>[...l,message])
});
}
useEffect(()=>{
//the second problem is here
socket.on('rec',(data)=>{
setList(l=>[...l,data])
})
},[])
return (
<div>
<div>
{list!==[] && list!==null && list.map((el,i)=>{
return <p key={i}>{el}</p>
})}
</div>
<div>
<form>
<input value={message} onChange={e=>setMessage(e.target.value)} />
<button onClick={sendMessage}>Send</button>
</form>
</div>
</div>
)
}
export default Room;
Server Side:
const io = new Server(server, { cors: { origin: '*'}});
app.use((req,res,next)=>{
res.header("Access-Control-Allow-Credentials", true);
next();
})
dotenv.config();
io.on("connection", (socket) => {
socket.emit('greeting', {
greeting: 'Hello Client'
})
socket.on("send", (data) => {
io.emit("rec", data);
console.log(data)
});
});
server.listen(process.env.PORT, err => err?console.log('server error'):console.log(`server is running on PORT ${process.env.PORT}`));
//the first problem is here
socket.emit('send',(data)=>{
setList(l=>[...l,message])
});
has to be
socket.emit('send', message, (data)=>{
setList(l=>[...l,message])
});
(you missed the argument, see https://socket.io/docs/v4/client-api/#socketemiteventname-args)
2)
useEffect(()=>{
//the second problem is here
socket.on('rec',(data)=>{
setList(l=>[...l,data])
})
},[])
it's not really a problem, because you add the same message here and in sendMessage, by using setList twice. I suppose for the chat app you need to change io.emit("rec", data); with a different response.

TypeError: msg.map is not a function

I'm trying to create a realtime online chat using React, node and mongodb. In my opinion, the chat shoudl work this way: client send the object of the message he created to the server to save it via rest(works properly) and via socket so the socketserver "broadcast" the message to every socket that is in the same room(socket on connection is put in a room according to something store in localstorage). So, other client in the same room should then recive the message, and happend it to the chat. But it is not workink. In fact, the following error shows up: Uncaught TypeError: msg.map is not a function
This is my react code:
import {useState, useEffect} from 'react';
import axios from 'axios';
import { io } from "socket.io-client";
const Chat = () => {
const [msg, setMsg] = useState([]);
const [message, setMessage] = useState('');
const socket = io("http://localhost:5050");
useEffect(() => {
if(v.group.name){
axios.get(`http://localhost:5050/chat/getmsg/${v.group.name}`)
.then(group => {
setMsg(group.data)
})
}
}, [v.group.name])
useEffect(() => {
if(localStorage.getItem('isG') === '1'){
socket.on("connect", () => {
socket.emit("groupName", {id:localStorage.getItem('gruop')})
})
socket.on("message", messageS => {
if(messageS.sender !== localStorage.getItem('user'))
setMsg(...msg, messageS)
})
}
// eslint-disable-next-line
}, [socket])
const sendMSG = (e) => {
e.preventDefault();
if(message !== ""){
axios.post("http://localhost:5050/chat/sendmsg", {name:v.group.name, sender:localStorage.getItem('user'), text:message})
.then(() => {
setMessage('');
socket.emit("message", {name:v.group.name, sender:localStorage.getItem('user'), text:message})
setMsg(...msg, {name:v.group.name, sender:localStorage.getItem('user'), text:message})
});
}
}
return <div className="containerLogin1">
<div>
<h3>Chat Name</h3>
</div>
<div className="chatSpace">
{
msg.map((m) => {
return <p key={m._id}>{m.text}</p>
})
}
</div>
<form className="sMSG">
<input type="input" style={{'border':'2px solid black'}} value={message} onChange={(e) => setMessage(e.target.value)}/>
<button className="buttSend" onClick={sendMSG} spellCheck="false">Send</button>
</form>
</div>
}
And this is server code, but I think that he is working fine:
....
const httpServer = app.listen(port, () => {console.log(`Server listening on port ${port}`)});
const { Server } = require("socket.io");
const io = new Server(httpServer, {
cors : {
origin: "*",
methods:["GET", "POST"]
}
} );
io.on("connection", (socket) => {
let currentRoom = "";
socket.on("groupName", msg => {
socket.join(msg.id + "")
currentRoom = msg.id
})
socket.on("text-change", newText => {
io.to(currentRoom).emit( "text-change", {text:newText, emitter:socket.id})
})
socket.on("message", message => {
io.to(currentRoom).emit("message", message);
})
})
I tryied with a bounch of console.log to see where the error could be, but I couldn't find out. It seems that somewhere in the code, msg turns from an array to an object, and so map function crashes. Can someone please help me? Thanks
You have these 2 lines in your code where you are trying to copy the last array and add new object to it:
setMsg(...msg, messageS);
And:
setMsg(...msg, {name:v.group.name, sender:localStorage.getItem('user'), text:message});
These parts are the problem, you should add [] around them. So:
setMsg([...msg, messageS]);
setMsg([...msg, {name:v.group.name, sender:localStorage.getItem('user'), text:message}]);

Browsers hangs while using Socket.io with ReactJS and ExpressJS

Problem:
I am working on a chat application. When I send more than 9-10 requests, the browser slows down and eventually, it just hangs. On refreshing the page, everything is back to normal.
I searched the socket.io documentation but couldn't get any solution regarding this matter.
Code:
Here is my Backend Express.JS code:
index.js
const express = require("express");
const { addUser, getUser, deleteUser, getAllUsers } = require("./users_api");
const app = express();
const server = require("http").createServer(app);
const io = require("socket.io")(server, {
cors: {
origin: "http://localhost:3000",
methods: ["GET", "POST"],
},
});
const port = 5000 || process.env.PORT;
io.on("connection", (socket) => {
socket.on("join", ({ name, room }, callback) => {
const { err, user } = addUser({ id: socket.id, name, room });
if (err) return callback(err);
if (user) {
socket.emit("message", {
user: "admin",
text: `${user.name} has entered the room ${user.room}.`,
});
socket.broadcast.to(user.room).emit("message", {
user: "admin",
text: `${user.name} has joined the room.`,
});
socket.join(user.room);
io.to(user.room).emit("users", {
room: user.room,
users: getAllUsers(user.room),
});
callback();
}
});
socket.on("client_message", (msg) => {
const user = getUser(socket.id);
if (user) io.to(user.room).emit("message", { user: user.name, text: msg });
});
socket.on("disconnect", () => {
console.log(6);
const user = deleteUser(socket.id);
if (user) {
io.to(user.room).emit("message", {
user: "admin",
text: `${user.name} has left the room.`,
});
io.to(user.room).emit("users", {
room: user.room,
users: getAllUsers(user.room),
});
}
});
});
server.listen(port, () => console.log(`Server started at port ${port}.`));
users_api.js:
const current_users = [];
const addUser = ({ id, name, room }) => {
name = name.trim().toLowerCase().split(" ").join("_");
room = room.trim().toLowerCase();
const exist_user = current_users.find(
(user) => name === user.name && room === user.room
);
if (exist_user)
return {
err: "User with this name already exists.",
};
current_users.push({ id, name, room });
return { user: { id, name, room } };
};
const deleteUser = (id) => {
const index = current_users.findIndex((user) => user.id === id);
if (index !== -1) return current_users.splice(index, 1)[0];
};
const getUser = (id) => current_users.find((user) => user.id === id);
const getAllUsers = (room) =>
current_users.filter((user) => user.room === room);
module.exports = { addUser, deleteUser, getUser, getAllUsers };
Front-end Code:
import io from "socket.io-client";
import React, { useEffect, useRef, useState } from "react";
import queryString from "query-string";
import "./ChatPage.css";
const END_POINT = "http://localhost:5000";
const ChatPage = (props) => {
const [message, setMessage] = useState("");
const [messages, setMessages] = useState([]);
const [users, setUsers] = useState([]);
const socket = useRef(null);
const handleSubmit = () => {
socket.current.emit("client_message", message);
setMessage("");
};
useEffect(() => {
const { name, room } = queryString.parse(props.location.search);
socket.current = io(END_POINT);
socket.current.emit("join", { name, room }, (error) => {
if (error) alert(error);
});
return () => {
socket.current.disconnect();
socket.current.off();
};
}, [props.location.search, END_POINT]);
useEffect(() => {
socket.current.on("message", (msg) => {
setMessages([...messages, msg]);
});
socket.current.on("users", ({ users }) => {
setUsers(users);
});
}, [messages.length]);
return (
<div className="chat-room-container">
<div className="chat-room-left">
<ul>
{messages.map((message, i) => (
<li key={i}>
{message.name}
{message.text}
</li>
))}
</ul>
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
<button type="button" onClick={handleSubmit}>
Submit
</button>
</div>
<div className="chat-room-right">
<ul>
{users.map((user, i) => (
<li key={i}>{user.name}</li>
))}
</ul>
</div>
</div>
);
};
export default ChatPage;
I don't understand what's wrong, please help.
Instead of sending a message on event or broadcast try to send list of messages to particular user or all users list with their messages belonging to same room. This will leads to less render in frontend.
For example,
Your Frontend code will change like,
useEffect(() => {
socket.current.on("message", (msgList) => {
setMessages(msgList);
});
socket.current.on("users", ({ users }) => {
setUsers(users);
});
}, [messages.length]);
Also if different rooms then there occurs a problem to filter the messages. So to solve this there are two possible ways:
When you send filter data from server.
Filter data in client according to user name and room name.
As this solution is not a good practice as every time the whole list of message will come and applying filter on that will increase time complexity. But you can reduce to some extent by making proper structure of data state on server.
I think this will help you.
You are creating new socket connection on every useEffect, so after ten messages, you have ten connections.
socket = io(END_POINT);
I store the created socket connection in useRef - so if it is created again, it is overwritten with the new one - and it does not duplicate.
const socketConection = useRef(null)
useEffect(() => {
socketConnection.current = io(END_POINT)
}, [deps])
Of course, in all following uses you have to use socketConection.current

Socket.io with React behaves weirdly after 5 messages

I am trying to build a basic chat application using socket.io and react but have ran into a weird problem. The app works like it is expected to for the first 5 messages and then after that the 6th message takes too long to load and often some of the previous messages don't show up in the chat box. Would be glad if someone could help.
Here is the code I have for backend:
const express = require('express');
const app = express();
const http = require('http').createServer(app);
const io = require('socket.io')(http);
io.on('connection', socket => {
socket.on('message', ({ name, message }) => {
io.emit('message', { name, message });
console.log(message);
console.log(name);
});
});
http.listen(4000, function () {
console.log('listening on port 4000');
});
Here is the code I have in my App.js:
import React, { useState, useEffect } from 'react';
import io from 'socket.io-client';
const App = props => {
const socket = io.connect('http://localhost:4000');
const [details, setDetails] = useState({ name: '', message: '' });
const [chat, setChat] = useState([]);
useEffect(() => {
socket.on('message', ({ name, message }) => {
setChat([...chat, { name, message }]); //same as {name:name,message:message}
});
});
const onMessageSubmit = e => {
e.preventDefault();
const { name, message } = details;
socket.emit('message', { name, message });
setDetails({ name, message: '' });
};
return (
<div>
<form onSubmit={onMessageSubmit}>
<input
type='text'
value={details.name}
onChange={e => setDetails({ ...details, name: e.target.value })}
/>
<input
type='text'
value={details.message}
onChange={e => setDetails({ ...details, message: e.target.value })}
/>
<button>Send</button>
</form>
<ul>
{chat &&
chat.map((chat, index) => (
<li key={index}>
{chat.name}:{chat.message}
</li>
))}
</ul>
</div>
);
};
export default App;
That's because every time, you update the state, useEffect callback runs, basically you subscribe to message again and again.
And after few iterations, you've multiple subscriptions trying to update the same state. And because of the setState's asynchronous nature, you're seeing the weird behavior.
You need to subscribe only once, you can do that by passing empty dependency argument to useEffect which will make it work like componentDidMount
useEffect(() => {
socket.on('message', ({ name, message }) => {
setChat([...chat, { name, message }]);
});
}, []);
Edit - To handle the asynchronity and take in account the previous chat, you need to setState via callback
useEffect(() => {
socket.on("message", ({ name, message }) => {
setChat((prevChat) => prevChat.concat([{ name, message }]));
});
}, []);
You might want to cleanup when your component un-mounts. Please have a look at the official docs.

Resources