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
}, []);
Related
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);
sometimes its post 3-4 times.
probably its about rendering. i moved init function from container.jsx to app.jsx not worked.
tried to delete cache. not worked
as you can see from console log. is chatlist rendered twice? or something like that
using socket.broadcast.emit on backend. not working
client code
import { useEffect } from "react";
import io from "socket.io-client";
let socket;
export const init = () => {
console.log("Connecting...");
socket = io.connect("http://localhost:3000", {
transports: ["websocket"],
});
socket.on("connect", () => console.log("Connected!"));
};
export const sendMessage = (message) => {
if (socket) {
socket.emit("new-message", message);
}
};
export const onNewMessage = (callback) => {
if (!socket) return;
socket.on("receive-message", (message) => {
callback(message);
console.log("Received message: ", message);
});
};
server code
const app = require("express")();
const http = require("http").Server(app);
const io = require("socket.io")(http);
const cors = require("cors");
const Messages = require("./lib/Messages");
app.use(cors());
app.get("/", (req, res) => {
res.end("Merhaba Socket.IO");
});
io.on("connection", (socket) => {
console.log("a user connected");
Messages.list((data) => {
console.log(data);
socket.emit("message-list", data);
});
socket.on("new-message", (message) => {
console.log(message);
Messages.upsert({ message });
socket.broadcast.emit("receive-message", message);
});
socket.on("disconnect", () => console.log("a user disconnected"));
});
http.listen(process.env.PORT || "3000", () => {
console.log("listening on *:3000");
});
chatlist.jsx
import { useChat } from "../context/ChatContext";
import styles from "./style.module.css";
import ChatItem from "./ChatItem";
export default function ChatList() {
const { messages } = useChat();
console.log(messages);
return (
<div className={styles.chatlist}>
<div>
{messages.map((item, key) => (
console.log("Message: ",item),
<ChatItem key={key} value={item} />
))}
</div>
</div>
);
}
its because of the useEffect bug in last version of react
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");
});
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.
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");
});
});