iam new to the websocket, and i'm currently wanting to create a chat app using socket.io
i follow the documentation on the socket.io website and successfully connect the server to the client
but as i want to emit from the server to the client or vise versa i found out that my client is not connected to the server as the property disconnected = true
and my guess is that because of this i can't use the emit event
can someone explain whats going on?
note: both using version 2.2.0 of socket.io
here is the screenshot of whats happening
server
and the code of the server
const app = require('express')()
const http = require('http').createServer(app)
const io = require('socket.io')(http)
io.on('connection', socket => {
console.log(socket.connected)
socket.on('message', ({ name, message }) => {
console.log(name)
console.log(message)
io.emit('message', { name, message })
})
})
http.listen(4000, function () {
console.log('listening on port 4000')
})
and here is the screenshot of the client
client
and here is the code of the client
import React, { useEffect, useRef, useState } from "react"
import io from "socket.io-client"
import "./App.css"
function App() {
const [state, setState] = useState({ message: "", name: "" })
const [chat, setChat] = useState([])
const socketRef = useRef()
useEffect(
() => {
socketRef.current = io("http://127.0.0.1:4000")
socketRef.current.connect()
console.log(socketRef.current)
socketRef.current.on('connect', function () {
console.log("masuk")
});
socketRef.current.on("message", ({ name, message }) => {
setChat([...chat, { name, message }])
})
return () => socketRef.current.disconnect()
},
[chat]
)
const onTextChange = (e) => {
setState({ ...state, [e.target.name]: e.target.value })
}
const onMessageSubmit = (e) => {
const { name, message } = state
console.log(name)
console.log(message)
socketRef.current.emit("message", { name, message })
e.preventDefault()
setState({ message: "", name })
}
const renderChat = () => {
return chat.map(({ name, message }, index) => (
<div key={index}>
<h3>
{name}: <span>{message}</span>
</h3>
</div>
))
}
return (
<div className="card">
<form onSubmit={onMessageSubmit}>
<h1>Messenger</h1>
<div className="name-field">
<TextField name="name" onChange={(e) => onTextChange(e)} value={state.name} label="Name" />
</div>
<div>
<TextField
name="message"
onChange={(e) => onTextChange(e)}
value={state.message}
id="outlined-multiline-static"
variant="outlined"
label="Message"
/>
</div>
<button>Send Message</button>
</form>
<div className="render-chat">
<h1>Chat Log</h1>
{renderChat()}
</div>
</div>
)
}
export default App
i got it already, its because the version is not compatible between the server and the client
Related
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);
});
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}]);
(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)
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.
I have created a chat app using nodejs/express, mongodb, reactjs. When I type in the chat box and on clicking send button the data get's stored inside mongodb but how can I display it on client side. In code below I am not able to display it on client side. In server.js I am not able to emit the messages. What I am doing wrong in server.js ? The data is not reaching frontend from backend/database.
Code:
server.js:
const express = require('express');
const mongoose = require('mongoose');
const socket = require('socket.io');
const message = require('./model/message')
const app = express();
const mongoURI = require('./config/keys').mongoURI;
mongoose.connect(mongoURI, {useNewUrlParser: true})
.then()
.catch( err => console.log(err));
let db = mongoose.connection;
const port = 5000;
let server = app.listen(5000, function(){
console.log('server is running on port 5000')
});
let io = socket(server);
io.on("connection", function(socket){
console.log("Socket Connection Established with ID :"+ socket.id)
socket.on('disconnect', function(){
console.log('User Disconnected');
});
let chat = db.collection('chat');
socket.on('SEND_MESSAGE', function(data){
let message = data.message;
let date = data.date;
// Check for name and message
if(message !== '' || date !== ''){
// Insert message
chat.insert({message: message, date:date}, function(){
socket.emit('output', [data]);
});
}
});
//Code below this is not working:
chat.find().limit(100).sort({_id:1}).toArray(function(err, res){
if(err){
throw err;
}
// Emit the messages
socket.emit('RECEIVE_MESSAGE', res);
});
})
chat.js:
import React, { Component } from 'react'
import './chat.css'
import io from "socket.io-client";
export default class Chat extends Component {
constructor(props){
super(props);
this.state = {
message: '',
date: '',
messages: []
};
const socket = io('localhost:5000');
this.sendMessage = event => {
event.preventDefault();
if(this.state.message !== ''){
socket.emit('SEND_MESSAGE', {
message: this.state.message,
date: Date.now()
});
this.setState({ message: '', date: '' });
}
};
socket.emit('RECEIVE_MESSAGE', data => {
addMessage(data);
});
const addMessage = data => {
console.log(data);
this.setState({
messages: [...this.state.messages, data],
});
console.log(this.state.message);
console.log(this.state.messages);
};
}
render() {
return (
<div>
<div id="status"></div>
<div id="chat">
<div className="card">
<div id="messages" className="card-block">
{this.state.messages.map((message, index) => {
return (
<div key={index} className="msgBox"><p className="msgText">{message.message}</p></div>
)
})}
</div>
</div>
<div className="row">
<div className="column">
<input id="inputmsg" type="text" placeholder="Enter Message...."
value={this.state.message} onChange={ev => this.setState({message: ev.target.value})}/>
</div>
<div className="column2">
<button id="send" className="button" onClick={this.sendMessage}>Send</button>
</div>
</div>
</div>
</div>
)
}
}
Screenshot of mongo shell:
Mongoose documentation specifies the correct usage of .find() method . You can find it here.
To cut the chase you need to give the method an object model that you are looking for. So for example if you were looking for objects with specific date field you could use:
chat.find({ "date": <some-date> }, function(err, objects) { ... });
If you want to fetch all object from the collection you can use:
chat.find({}, function(err, objects) { ... });