Why does useEffect() not updating messages object on new message? - reactjs

I am new and I am following a tutorial of socket.io for real-time chatting. Users join the room and then a text box will appear. When Users type the message and send it, the messages object must store the message.
message is sending from the server but on the client side messages are not updating with the new message. Is useEffect not working? Or how can I get the array updated?
Chat.js
import queryString from 'query-string';
import io from 'socket.io-client';
let socket;
const Chat = ({ location }) => {
const [name, setName] = useState('');
const [room, setRoom] = useState('');
const [message, setMessage] = useState('');
const [messages, setMessages] = useState([]);
const ENDPOINT = 'localhost:5000';
useEffect(() => {
const { name, room } = queryString.parse(location.search);
socket = io(ENDPOINT);
setName(name);
setRoom(room);
socket.emit('join', { name, room }, () => {
});
return () => {
socket.disconnect();
}
},[ENDPOINT, location.search]);
useEffect(() => {
socket.on('message', (message) => {
setMessages([...messages, message]);
});
}, [messages]);
const sendMessage = (event) => {
event.preventDefault();
if (message) {
socket.emit('sendMessage', message, () => setMessage(''));
}
console.log(message, messages);
}
return (
<div className="outerContainer">
<div className="container">
<input
value={message}
onChange={event => setMessage(event.target.value)}
onKeyPress={event => event.key === 'Enter' ? sendMessage(event) : null}
/>
</div>
</div>
);
};
export default Chat;
index.js
const socketio = require('socket.io');
const http = require('http');
const { addUser, removeUser, getUser, getUsersInRoom } = require('./users');
const PORT = process.env.PORT || 5000;
const router = require('./router');
const app = express();
const server = http.createServer(app);
const io = socketio(server, {
cors: {
origin: "http://localhost:3000",
methods: ["GET", "POST"]
},
});
io.on('connection', (socket) => {
socket.on('join', ({ name, room }, callback) => {
const { error, user} = addUser({ id: socket.id, name, room});
if (error) {
return callback(error);
}
socket.emit('message', { user: 'admin', text: `${user.name}, welcome to the room ${user.room}`});
socket.broadcast.to(user.room).emit('message', { user: 'admin', text: `${user.name}, has joined`});
socket.join( user, room);
callback();
});
socket.on('sendMessage', (message, callback) => {
const user = getUser(socket.id);
io.to(user.room).emit('message', { user: user.name, text: message});
callback();
});
socket.on('disconnect', () => {
console.log('User had left!!!');
});
});
app.use(router);
server.listen(PORT, () => console.log(`Server has started ${PORT}`));```

The problem is that you are updating the messages state in useEffect() which has messages as one of the dependencies. U can fix this problem by passing a callback to setMessages() like this:
setMessages(prevMessages => [...prevMessages, message])
and remove the messages dependency from the dependency array.

Related

Getting duplicate messages with React + Socket.IO chat app

Attempting to create a simple React chat app, but I'm running into an issue where duplicate messages appear. I run the server with node server.js, in a separate terminal run the client with npm start, then open two windows at localhost:3000. I can send a message, but any message I send is received x2. For example (after submitting the form with "i only sent this once" on one window, this is what appears in the second window).:
I did console.log on the server-side, and only one message is being received from the server.
Here is my server.js file:
const express = require("express");
const socketio = require("socket.io");
const http = require("http");
const cors = require("cors");
const PORT = process.env.PORT || 5001;
const app = express();
const server = http.createServer(app);
const io = socketio(server);
app.use(cors());
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
io.on("connection", (socket) => {
socket.on("connection", () => {});
socket.on("send-message", (message) => {
console.log(message);
socket.broadcast.emit("message", message);
});
});
Here's my socket.js file:
import io from "socket.io-client";
const SERVER = "http://127.0.0.1:5001";
const connectionOptions = {
forceNew: true,
reconnectionAttempts: "Infinity",
timeout: 10000,
transports: ["websocket"],
};
let socket = io.connect(SERVER, connectionOptions);
export default socket;
Here's my App.js file:
import { useState, useEffect } from "react";
import "./App.css";
import socket from "./socket";
const ChatWindow = () => {
const [message, setMessage] = useState("");
const [messages, setMessages] = useState([]);
const handleMessageChange = (event) => {
setMessage(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
socket.emit("send-message", { message: message });
};
useEffect(() => {
socket.on("connection", null);
socket.on("message", (payload) => {
setMessages((prev) => {
return [...prev, payload.message];
});
});
}, []);
return (
<div>
{messages.map((message) => {
return <h3>message: {message}</h3>;
})}
<form onSubmit={handleSubmit}>
<input value={message} onChange={handleMessageChange}></input>
<button type="submit">Send Message</button>
</form>
<h1>Chat Window</h1>
</div>
);
};
function App() {
return (
<div className="App">
<ChatWindow />
</div>
);
}
export default App;
As punjira answered in a comment, removing the event listener for the useEffect worked.
Specifically, before:
useEffect(() => {
socket.on("connection", null);
socket.on("message", (payload) => {
setMessages((prev) => {
return [...prev, payload.message];
});
});
}, []);
After:
useEffect(() => {
socket.on("connection", null);
socket.on("msg", (payload) => {
setMessages((prev) => {
return [...prev, payload.msg];
});
});
return function cleanup() {
socket.removeListener("msg");
};
}, []);
If you are using UseEffects Hooks you need to clean up
Before :
useEffect(() => {
socket.on("connection", null);
socket.on("message", (payload) => {
setMessages((prev) => {
return [...prev, payload.message];
});
});
}, []);
You can add this line to clean up :
useEffect(() => {
socket.on("connection", null);
socket.on("message", (payload) => {
setMessages((prev) => {
return [...prev, payload.message];
});
});
return () => socket.off("message"); // add this line to your code
}, []);

socketio cant get new messages from server-side | react

emitting messages to server working fine but when i try to get message from server its not working.
tried different useEffect dependencies (incomingMessage etc.), after some search all sources using socket as dependency.
in server side messages is appear tring to send message to client
client
import io from "socket.io-client";
import { useEffect, useState } from "react";
const socket = io("http://localhost:3001");
function App() {
const [loading, setLoading] = useState(true);
const [message, setMessage] = useState("");
const [incomingMessage, setIncomingMessage] = useState([]);
useEffect(() => {
socket.on("connect", () => {
setLoading(false);
});
},[]);
const sendMessage = (e) => {
e.preventDefault();
socket.emit("message", message);
setMessage("");
};
useEffect(() => {
socket.on("new-message", (data) => {
console.log("new message", data);
setIncomingMessage(data);
});
console.log("incoming message", incomingMessage);
}, [socket]);
if (loading) {
return <div>Loading...</div>;
}
return (
<div>
<h1>{socket.id}</h1>
<input value={message} onChange={(e) => setMessage(e.target.value)} />
<button onClick={sendMessage}>Send</button>
<p>{incomingMessage}</p>
</div>
);
}
export default App;
server
import express from "express";
import { createServer } from "http";
import { Server } from "socket.io";
const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, {
cors: {
origin: "http://localhost:3000",
methods: ["GET", "POST"],
},
});
io.on("connection", (socket) => {
socket.on("message", (data) => {
console.log(data);
socket.emit("new-message", data);
});
});
httpServer.listen(3001);

How to prevent react is rendering 2 times

I am facing a problem where the react is rendering 2 times but I do not see any problem in the code or it is come from the socket? I noticed this problem when consoling and when displaying data, the data duplicate. Below I put my client react code with the server socket code.
React client
import "./App.css";
import io from "socket.io-client";
import { useEffect, useState } from "react";
const socket = io.connect("http://localhost:3001");
function App() {
const [text, setText] = useState("");
const [room, setRoom] = useState("");
const [data, setData] = useState([]);
const [action, setAction] = useState(true);
console.log("data", data);
console.log(text);
console.log(room);
const sendMessage = () => {
socket.emit("send_message", { text, room });
};
const joinRoom = () => {
if (room !== "") {
socket.emit("join_room", room);
}
};
useEffect(() => {
socket.on("receive_message", (msg) => {
setData((prev) => [
...prev,
{
message: msg,
},
]);
});
}, [action]);
return (
<div className="App">
{data.map((da) => (
<p>{da.message}</p>
))}
<input
type="text"
placeholder="room no"
onChange={(e) => {
setRoom(e.target.value);
}}
></input>
<button onClick={joinRoom}>Join</button>
<br />
<input
type="text"
placeholder="message .."
onChange={(e) => {
setText(e.target.value);
}}
></input>
<button onClick={sendMessage}>Send Message</button>
</div>
);
}
export default App;
Socket server
const express = require("express");
const app = express();
const http = require("http");
const { Server } = require("socket.io");
const cors = require("cors");
app.use(cors());
const server = http.createServer(app);
const io = new Server(server, {
cors: {
origin: "http://localhost:3000",
methods: ["GET", "POST"],
},
});
io.on("connection", (socket) => {
// console.log(`User Connected ${socket.id}`);
socket.on("join_room", (data) =>{
socket.join(data);
})
socket.on("send_message", (data) => {
socket.to(data.room).emit("receive_message", data.text);
})
});
server.listen(3001, () => {
console.log("SERVER OK");
});

emitting action with socketio into different react components

I am trying to emit a msg into different react components and update all components with new value.
server:
const express = require('express');
const socketio = require('socket.io');
const http = require('http');
const {addPlayer, removePlayer, getPlayer, getPlayerssInRoom, addRoom} = require('./players');
const cors = require('cors');
const PORT = process.env.PORT || 5000;
const router = require('./router');
const app = express();
const server = http.createServer(app);
const io = socketio(server);
app.use(cors());
app.use(router);
io.on('connection', (socket) => {
socket.on('question', (question) => {
socket.emit('setQuestion', question);
socket.broadcast.to('rtd').emit('setQuestion', question);
io.sockets.in('rtd').emit('setQuestion', question);
});
socket.on('disconnect', () => {
console.log('User had left');
});
});
app.use(router);
server.listen(PORT, () => console.log(`Server has started on port ${PORT}`));
First component where I emit an action to the server:
import React, {useEffect, useState} from 'react';
import Button from 'react-bootstrap/Button';
import io from 'socket.io-client';
let socket;
const Admin = () => {
const [currentQuestion, setCurrentQuestion] = useState('');
const ENDPOINT = 'localhost:5000';
socket = io(ENDPOINT);
const publishQuestion = () => {
console.log('trying');
socket.emit('question', currentQuestion);
}
useEffect(() => {
socket = io(ENDPOINT);
socket.on('connect', function (data) {
console.log('socket: ', socket.id);
socket.on('answer', (answer) => {
console.log('do i get here');
})
socket.on('setQuestion', (newQuestion) => {
setCurrentQuestion(newQuestion);
console.log(newQuestion);
})
});
}, [currentQuestion]);
return (
<div className="justify-content-center">
<h1>Admin</h1>
<input onChange={(event)=>setCurrentQuestion(event.target.value)}/>
<Button onClick={()=> publishQuestion()}>Publish word</Button>
</div>
)
}
export default Admin;
Second component where I am having issue to listen to the emit that comes from the server:
import React, {useState} from 'react';
import io from 'socket.io-client';
let socket;
const Player = () => {
const [currentQuestion, setCurrentQuestion] = useState('');
const ENDPOINT = 'localhost:5000';
socket = io(ENDPOINT);
socket.on('connect', function (data) {
console.log('socket: ', socket.id);
socket.on('answer', (answer) => {
console.log('do i get here');
})
socket.on('setQuestion', (newQuestion) => {
setCurrentQuestion(newQuestion);
console.log(newQuestion);
})
});
return (
<h1>player</h1>
)
}
export default Player;
Anyone has an idea why the Player component not picking on the server emit (socket.on('setQuestion', (newQuestion))?
I recommend you to keep the socket client reference in one place. This way you have the same socket reference in your components and the components state updates won't affect it:
socket-client.js
import io from "socket.io-client";
const ENDPOINT = "localhost:5000";
export const socket = io(ENDPOINT);
Admin.js
import { socket } from "./socket-client";
const Admin = () => {
const [currentQuestion, setCurrentQuestion] = useState('');
const publishQuestion = () => {
socket.emit('question', currentQuestion);
}
useEffect(() => {
socket.on('connect', function (data) {
console.log('socket: ', socket.id);
socket.on('answer', (answer) => {
console.log('do i get here');
})
socket.on('setQuestion', (newQuestion) => {
setCurrentQuestion(newQuestion);
console.log(newQuestion);
})
});
}, []); //--> empty dependency
}
Player.js
import { socket } from "./socket-client";
const Player = () => {
const [currentQuestion, setCurrentQuestion] = useState('');
useEffect(()=> {
socket.on('connect', function (data) {
console.log('socket: ', socket.id);
socket.on('answer', (answer) => {
console.log('do i get here');
})
socket.on('setQuestion', (newQuestion) => {
setCurrentQuestion(newQuestion);
console.log(newQuestion);
})
});
}, [])
}
EDIT /
Use io.sockets.in if you want to emit to all socket in that room.
Use socket.broadcast.to to emit to all socket in that room except it.
server.js
io.on("connection", (socket) => {
socket.join('rtd'); //--> join to rtd room;
socket.on("question", (question) => {
// socket.broadcast.to("rtd").emit("setQuestion", question);
io.sockets.in("rtd").emit("setQuestion", question);
});
socket.on("disconnect", () => {
console.log("User had left");
});
});

How can I upload a image file using and react Socket-IO?

i want to make a chat app with react and socket.io
i downloaded code from https://github.com/adrianhajdin/project_chat_application.
I want to add upload file function using socketio_file_upload
but when i upload the img. there is nothing happen..
this is my code
index.js (server)
const express = require('express');
const socketio = require('socket.io');
const http = require('http');
const siofu = require("socketio-file-upload");
const { addUser, removeUser, getUser, getUsersInRoom } = require('./users');
const PORT = process.env.PORT || 5000
const router = require('./router')
const app = express()
console.log(app,'------------------app----------')
const server = http.createServer(app)
const io = socketio(server)
io.on('connection', (socket)=>{
const uploader = new siofu();
uploader.dir = "/uploads";
uploader.listen(socket);
uploader.on("saved", function(event){
});
uploader.on("error", function(event){
});
socket.on('join', ({name, room}, callback)=>{
const { user, error } = addUser({ id:socket.id, name, room })
if(error) {
return callback(error)
}
//admin system정보 여기서
socket.emit('message', { user:'admin', text:`${user.name}, welcome to the room ${user.room}` })
//braodcast는 room에 있는 모든 사람에게 msg를 보낸다.
socket.broadcast.to(user.room).emit('message', { user: 'admin', text: `${user.name} has joined!` });
socket.join(user.room)
io.to(user.room).emit('roomData', {room: user.room, users: getUsersInRoom(user.room)})
callback()
})
socket.on('sendMessage', (message, callback) => {
const user = getUser(socket.id);
io.to(user.room).emit('message', { user: user.name, text: message });
io.to(user.room).emit('roomData', { room: user.room, users: getUsersInRoom(user.room)});
callback();
});
socket.on('disconnect', () => {
const user = removeUser(socket.id)
io.to(user.room).emit('message', { user:'admin', text: `${user.name} has left`})
})
})
app.use(siofu.router)
server.listen(PORT, ()=> console.log('---SERVER START---'))
Chat.jsx
import React, { useState, useEffect, useRef } from 'react';
import queryString from 'query-string'
import io from 'socket.io-client'
import './chat.css'
import InfoBar from '../InfoBar/InfoBar'
import Messages from '../Messages/Messages'
import Input from '../Input/Input'
let socket ='default'
const Chat = ({location}) => {
const [name, setName] = useState('');
const [room, setRoom] = useState('');
const [message, setMessage] = useState('')
const [messages, setMessages] = useState([])
const END_POINT = 'localhost:5000'
//componentdidmount와 같다
useEffect(()=>{
const {name, room} = queryString.parse(location.search)
socket = io(END_POINT)
setName(name)
setRoom(room)
//server에 join을 통해서 데이터를 보냄
socket.emit('join', {name, room}, (res)=>{
})
//useEffect()애서 return 은 unmount와 같다. 즉 user leave일 때 사용!
return () =>{
socket.emit('disconnect')
socket.off()
}
},[END_POINT, location.search])
useEffect(() => {
socket.on('message', (message) =>{
setMessages([...messages, message])
})
// uploader.listenOnInput(document.getElementById("siofu_input"));
}, [messages])
const sendMessage = (event) => {
event.preventDefault();
if(message) {
socket.emit('sendMessage', message, () => setMessage(''));
}
}
return (
<div className="outerContainer">
<div className="container">
<InfoBar room={room} />
<Messages messages={messages} name={name} />
<Input socket={socket} message={message} setMessage={setMessage} sendMessage={sendMessage} />
</div>
</div>
);
};
export default Chat;
Input.jsx
import React, { Component } from 'react';
import SocketIOFileUpload from '../../../../server/node_modules/socketio-file-upload'
import './input.css';
class Input extends Component {
uploadFile = (e) => {
const {socket} = this.props
console.log("uploading");
// const files = e.target.files;
const siofu = new SocketIOFileUpload(socket);
siofu.listenOnInput(document.getElementById("siofu_input"));
};
render() {
const { setMessage, sendMessage, message, socket } = this.props
return (
<form className="form">
<input
className="input"
type="text"
placeholder="Type a message..."
value={message}
onChange={({ target: { value } }) => setMessage(value)}
onKeyPress={event => event.key === 'Enter' ? sendMessage(event) : null}
/>
<input type="file" id="siofu_input" onChange={this.uploadFile.bind(this)}/>
<button className="sendButton" onClick={e => sendMessage(e)}>Send</button>
</form>
);
}
}
export default Input;
users.js
const users = [];
const addUser = ({ id, name, room }) => {
name = name.trim().toLowerCase();
room = room.trim().toLowerCase();
const existingUser = users.find((user) => user.room === room && user.name === name);
if(!name || !room) return { error: 'Username and room are required.' };
if(existingUser) return { error: 'Username is taken.' };
const user = { id, name, room };
users.push(user);
return { user };
}
const removeUser = (id) => {
const index = users.findIndex((user) => user.id === id);
if(index !== -1) return users.splice(index, 1)[0];
}
const getUser = (id) => users.find((user) => user.id === id);
const getUsersInRoom = (room) => users.filter((user) => user.room === room);
module.exports = { addUser, removeUser, getUser, getUsersInRoom };
How can i upload img file to chat app....
At first, you must create "uploads" folder to upload files.
When I run the app on local, I created folder on to D drive and changed uploader.dir from '/uploads' to "uploads";
At second, when you select DOM, you used document.elementById, this is wrong, must use ref={(el)=>this.variable=el} and change to siofu.listenOnInput(this.variable);
At third, you did define upload logic in onChange event listener, have to define in componentDidMount().

Resources