On server side I have this nodejs app:
io.use((socket, next) => {
socket.name = socket.handshake.headers.name;
socket.room = socket.handshake.headers.room;
socket.user_id = socket.handshake.headers.user_id;
return next();
});
let all_sockets = [];
io.on("connection", (socket) => {
const ob = {
id: socket.id,
room: socket.room,
name: socket.name,
user_id: socket.user_id
};
all_sockets = all_sockets.filter(el => el.user_id !== socket.user_id);
all_sockets.push(ob);
socket.on("message", (data) => {
const recipients = all_sockets.filter(el => el.room === socket.room && el.id !== socket.id);
recipients.forEach(el => {
io.to(el.id).emit('private', data);
})
});
socket.on("disconnect", (reason) => {
all_sockets = all_sockets.filter(el => el.id !== socket.id);
});
});
On client side I have this component:
const Chat = () => {
const { username } = useParams();
const socket_url = `http://localhost:4000/`;
const [socket, setSocket] = useState(null);
const [chatMessages, setChatMessages] = useState([]);
const sendMessage = () => {
const msg = messageRef.current.value;
const message_object = {from: username, data: msg};
setChatMessages([...chatMessages, message_object]);
socket.emit('message', msg);
messageRef.current.value = '';
}
const connect = async () => {
try {
const body = {username: username};
const response = await fetch(`http://localhost:4000/login`, body);
const data = await response.json();
const sck = io(socket_url, {
path: "/socket.io",
autoConnect: true,
extraHeaders: {
room: data.user.room,
user_id: data.user._id,
name: data.user.name
}
});
sck.on('private', function(msg) {
const message_object = {from: 'user', data: msg};
setChatMessages([...chatMessages, message_object]);
});
setSocket(sck);
}
catch(exception){
console.log("exception",exception);
}
}
useEffect(() => {
connect();
}, []);
const messageRef = useRef('');
return (
<div>
<div>
<div>
{
JSON.stringify(chatMessages)
}
</div>
<div>
<input ref={messageRef} type="text" placeholder="Enter message"/>
</div>
<div>
<button onClick={sendMessage} >Send</button>
</div>
</div>
</div>
)
}
I open a browser window with user1 and another browser window with user2 and I type 3 messages from user1:
one
two
three
user1 sees this on the screen:
[{"from":"user1","data":"one"},{"from":"user1","data":"two"},{"from":"user1","data":"three"}]
However, user2 will see all the time that "chatMessages" is resetted. So after the thirth message, uer2 sees:
[{"from":"user","data":"three"}]
If user1 will enter "four", then user2 would see:
[{"from":"user","data":"four"}]
Also, when user2 types: "hello", then user1 will see on the screen:
[{"from":"user","data":"hello"}]
Why is the state array getting resetted? This happens only from inside the received function here:
sck.on('private', function(msg) {
const message_object = {from: 'user', data: msg};
setChatMessages([...chatMessages, message_object]);
});
CodePudding user response:
chatMessages
is closure captured. It always holds the same "value" it had during "on" attachment. Try using functional update. (And better to use it in both places, in the sck.on
and sendMessage
functions.)
setChatMessages(curr => [...curr, message_object]);