React does not update before another socket.io message - javascript

I have a node.js socket.io server that emits two messages with a 1000 ms delay. The messages are logged fine in the react app, however if I remove the 1000 ms delay, react does not update the first message's state before the second one is received.
The only way I was able to fix this issue was by using a global variable to store the first message. Is there a solution without a global variable?
Server code:
const app = express();
const http = require("http");
const server = http.createServer(app);
const { Server } = require("socket.io");
const io = new Server(server, {
cors: {
origin: "*",
},
});
io.on("connection", (socket) => {
socket.emit("data1", "First data");
setTimeout(() => {
socket.emit("data2", "Second data");
}, 1000);
});
server.listen(3001, () => {
console.log("listening on *:3001");
});
Working react app using global variable:
var data1;
export default function Main() {
const socket = useContext(SocketContext);
const onData1 = useCallback((data) => {
console.log("Received data1:", data);
data1 = data;
}, []);
const onData2 = useCallback(
(data) => {
console.log("Received data2:", data);
console.log("Data1 is:", data1);
},
[data1]
);
useEffect(() => {
console.log("Binding socket listeners...");
socket.on("data1", onData1);
socket.on("data2", onData2);
return () => {
console.log("Unbinding socket listeners...");
socket.off("data1", onData1);
socket.off("data2", onData2);
};
}, [socket, onData1, onData2]);
return <></>;
}
Not working react app:
export default function Main() {
const socket = useContext(SocketContext);
const [data1, setData1] = useState("");
const onData1 = useCallback((data) => {
console.log("Received data1:", data);
setData1(data);
}, []);
const onData2 = useCallback(
(data) => {
console.log("Received data2:", data);
console.log("Data1 is:", data1);
},
[data1]
);
useEffect(() => {
console.log("Binding socket listeners...");
socket.on("data1", onData1);
socket.on("data2", onData2);
return () => {
console.log("Unbinding socket listeners...");
socket.off("data1", onData1);
socket.off("data2", onData2);
};
}, [socket, onData1, onData2]);
return <></>;
}

Related

Socket.io-client on recieving event running useEffect two times

I'm trying to build a chat application,
The issue is whenever I send a emit an message to socket.io at the same time I recieve the same message two times. I got same message console.log two times.
Now this is my Server.js (Using express & Socket.io)
const express = require("express");
const app = express();
const http = require("http");
const cors = require("cors");
const { Server } = require("socket.io");
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);
console.log(`User with ID: ${socket.id} joined room: ${data}`);
});
socket.on("send_message", (data) => {
socket.to(data.room).emit("receive_message", data);
});
socket.on("disconnect", () => {
console.log("User Disconnected", socket.id);
});
});
server.listen(3001, () => {
console.log("SERVER RUNNING");
});
And this is my client's chat.js where I am sending prop of
const socket = io.connect("http://localhost:3001");
below is component
In this Chat component.
import React, { useEffect, useState } from "react";
function Chat({ socket, username, room }) {
useEffect(() => {
socket.on("receive_message", (data) => {
console.log(data);
});
return () => {
socket.off("receive_message", (data) =>
console.log(`receive_message off ${data}`)
);
};
}, [socket]);
return (
<>
<input
type="text"
placeholder="John..."
onChange={(event) => {
console.log(event.target.value);
}}
onKeyPress={(event) => {
if (event.key === "Enter") {
socket.emit("send_message", {
room: room,
author: username,
message: event.target.value,
});
}
}}
/>
</>
);
}
export default Chat;
After opening second tab I see two console.logs popping up with the same message. Can anybody tell me what I am doing wrong here or missing here?
I have tried removing React.StrictMode from index.js, it resolves the issue but I don't want to remove it. Also after shifting my react version from 18 to 17 it also resolves the issue, how I can tackle this issue in 18. Also I want to cover that issue in same chat.js component. Any help would be appreciated.
Thanks.
The problem is here:
useEffect(() => {
socket.on("receive_message", (data) => {
console.log(data);
});
return () => {
socket.off("receive_message", (data) =>
console.log(`receive_message off ${data}`)
);
};
}, [socket]);
You are properly trying to remove a listener on the return, but the problem is that you are removing the wrong listener--you have to pass the exact same function that you want to remove (because you can have two listeners on an event). This means that when the second render occurs during safe mode, you have two listeners added to that event.
Try this:
useEffect(() => {
const receiveMessage = (data) => {
console.log(data)
}
socket.on("receive_message", receiveMessage);
return () => {
socket.off("receive_message", receiveMessage);
};
}, [socket]);
That will make sure that the right function is removed. If this works, please make sure to mark my answer!

Trying to figure out how to use socket.io the correct way in a useEffect that is using an axios get request to fetch messages

So far i'm stuck on my useEffect that fetches all the current messages and renders the state accordingly. as of right now it doesn't render the new state until page is refreshed.
const Home = ({ user, logout }) => {
const history = useHistory();
const socket = useContext(SocketContext);
const [conversations, setConversations] = useState([]);
const [activeConversation, setActiveConversation] = useState(null);
const classes = useStyles();
const [isLoggedIn, setIsLoggedIn] = useState(false);
const addSearchedUsers = (users) => {
const currentUsers = {};
// make table of current users so we can lookup faster
conversations.forEach((convo) => {
currentUsers[convo.otherUser.id] = true;
});
const newState = [...conversations];
users.forEach((user) => {
// only create a fake convo if we don't already have a convo with this user
if (!currentUsers[user.id]) {
let fakeConvo = { otherUser: user, messages: [] };
newState.push(fakeConvo);
}
});
setConversations(newState);
};
const clearSearchedUsers = () => {
setConversations((prev) => prev.filter((convo) => convo.id));
};
const saveMessage = async (body) => {
const { data } = await axios.post("/api/messages", body);
return data;
};
const sendMessage = (data, body) => {
socket.emit("new-message", {
message: data.message,
recipientId: body.recipientId,
sender: data.sender,
});
};
const postMessage = async (body) => {
try {
const data = await saveMessage(body);
if (!body.conversationId) {
addNewConvo(body.recipientId, data.message);
} else {
addMessageToConversation(data);
}
sendMessage(data, body);
} catch (error) {
console.error(error);
}
};
const addNewConvo = useCallback(
(recipientId, message) => {
conversations.forEach((convo) => {
if (convo.otherUser.id === recipientId) {
convo.messages.push(message);
convo.latestMessageText = message.text;
convo.id = message.conversationId;
}
});
setConversations(conversations);
},
[setConversations, conversations],
);
const addMessageToConversation = useCallback(
(data) => {
// if sender isn't null, that means the message needs to be put in a brand new convo
const { message, sender = null } = data;
if (sender !== null) {
const newConvo = {
id: message.conversationId,
otherUser: sender,
messages: [message],
};
newConvo.latestMessageText = message.text;
setConversations((prev) => [newConvo, ...prev]);
}
conversations.forEach((convo) => {
console.log('hi', message.conversationId)
if (convo.id === message.conversationId) {
const convoCopy = { ...convo }
convoCopy.messages.push(message);
convoCopy.latestMessageText = message.text;
console.log('convo', convoCopy)
} else {
return convo
}
});
setConversations(conversations);
},
[setConversations, conversations],
);
const setActiveChat = useCallback((username) => {
setActiveConversation(username);
}, []);
const addOnlineUser = useCallback((id) => {
setConversations((prev) =>
prev.map((convo) => {
if (convo.otherUser.id === id) {
const convoCopy = { ...convo };
convoCopy.otherUser = { ...convoCopy.otherUser, online: true };
return convoCopy;
} else {
return convo;
}
}),
);
}, []);
const removeOfflineUser = useCallback((id) => {
setConversations((prev) =>
prev.map((convo) => {
if (convo.otherUser.id === id) {
const convoCopy = { ...convo };
convoCopy.otherUser = { ...convoCopy.otherUser, online: false };
return convoCopy;
} else {
return convo;
}
}),
);
}, []);
// Lifecycle
useEffect(() => {
// Socket init
socket.on("add-online-user", addOnlineUser);
socket.on("remove-offline-user", removeOfflineUser);
socket.on("new-message", addMessageToConversation);
return () => {
// before the component is destroyed
// unbind all event handlers used in this component
socket.off("add-online-user", addOnlineUser);
socket.off("remove-offline-user", removeOfflineUser);
socket.off("new-message", addMessageToConversation);
};
}, [addMessageToConversation, addOnlineUser, removeOfflineUser, socket]);
useEffect(() => {
// when fetching, prevent redirect
if (user?.isFetching) return;
if (user && user.id) {
setIsLoggedIn(true);
} else {
// If we were previously logged in, redirect to login instead of register
if (isLoggedIn) history.push("/login");
else history.push("/register");
}
}, [user, history, isLoggedIn]);
useEffect(() => {
const fetchConversations = async () => {
try {
const { data } = await axios.get("/api/conversations");
setConversations(data);
} catch (error) {
console.error(error);
}
};
if (!user.isFetching) {
fetchConversations();
}
}, [user]);
const handleLogout = async () => {
if (user && user.id) {
await logout(user.id);
}
};
return (
<>
<Button onClick={handleLogout}>Logout</Button>
<Grid container component="main" className={classes.root}>
<CssBaseline />
<SidebarContainer
conversations={conversations}
user={user}
clearSearchedUsers={clearSearchedUsers}
addSearchedUsers={addSearchedUsers}
setActiveChat={setActiveChat}
/>
<ActiveChat
activeConversation={activeConversation}
conversations={conversations}
user={user}
postMessage={postMessage}
/>
</Grid>
</>
);
};
this is the main part im working on, the project had starter code when i began and was told not to touch the backend so i know its something wrong with the front end code. i feel like im missing something important for the socket.io
import { io } from 'socket.io-client';
import React from 'react';
export const socket = io(window.location.origin);
socket.on('connect', () => {
console.log('connected to server');
});
export const SocketContext = React.createContext();
this is how i have the socket.io setup, if anyone could point me in the right direction that would be cool. I have been reading up on socket.io as much as I can but am still pretty lost on it.
Based on the assumption the backend is working properly...
const addNewConvo = useCallback(
(recipientId, message) => {
conversations.forEach((convo) => {
if (convo.otherUser.id === recipientId) {
convo.messages.push(message);
convo.latestMessageText = message.text;
convo.id = message.conversationId;
}
});
setConversations(conversations);
},
[setConversations, conversations],
);
setConversations(conversations);
This is an incorrect way to set a state using the state's variable, and such it wont do anything. Likely why your code wont change until refresh.
Suggested fix:
const addNewConvo = useCallback(
(recipientId, message) => {
setConversations(previousState => previousState.map(convo => {
if (convo.otherUser.id === recipientId) {
convo.messages.push(message)
convo.latestMessageText = message.text;
convo.id = message.conversationId;
return convo
}
return convo
}))
},
[setConversations, conversations],
);
note: even above could be done more efficiently since I made a deep copy of messages

Running socket.io on a domain

How do I run socket.io on a domain because my code works on localhost:3000 but not on a domain?
Does this have to be public in some way so that it can be used from anywhere?
Please help me with this.
Thanks you!
Script.js (client-side)
const socket = io("http://localhost:3000");
const messageContainer = document.getElementById('message-container')
const messageForm = document.getElementById('send-container')
const messageInput = document.getElementById('message-input')
const name = prompt('What is your name?')
appendMessage('You joined')
socket.emit('new-user', name)
socket.on('chat-message', data => {
appendMessage(`${data.name}: ${data.message}`)
})
socket.on('user-connected', name => {
appendMessage(`${name} connected`)
})
socket.on('user-disconnected', name => {
appendMessage(`${name} disconnected`)
})
messageForm.addEventListener('submit', e => {
e.preventDefault()
const message = messageInput.value
appendMessage(`You: ${message}`)
socket.emit('send-chat-message', message)
messageInput.value = ''
})
function appendMessage(message) {
const messageElement = document.createElement('div')
messageElement.innerText = message
messageContainer.append(messageElement)
}
Server.js (server-side)
const io = require('socket.io')(3000, {
cors: {
origin: "*",
},
});
console.log("Port 3000")
const users = {}
io.on('connection', socket => {
socket.on('new-user', name => {
users[socket.id] = name
socket.broadcast.emit('user-connected', name)
})
socket.on('send-chat-message', message => {
socket.broadcast.emit('chat-message', { message: message, name: users[socket.id] })
})
socket.on('disconnect', () => {
socket.broadcast.emit('user-disconnected', users[socket.id])
delete users[socket.id]
})
})
Change port to 80 (if you using http) or 443 (if you using https)
const io = require('socket.io')(80, {
cors: {
origin: "*",
},
});
--
const socket = io("http://website.com:80");

Socket.on is not firing

I am building a message board as a learning exercise and have working notifications and a chat app working with socket.io.
I am trying to integrate a basic video call functionality, but I am stumped on some basic preparations for that feature. My socket.on code isn't firing when the server emits the relevant function and I have no clue why. The console logs around the emits are all executing, so I know the code is being reached.
On the client my socket.on code is in the same component as my notifications and they are working and it is definitely mounted.
It's the userOff and receiveCall functions that aren't being executed (for whatever reason)...
Any help will be appreciated.
Server:
io.on('connection', (socket) => {
socket.emit('messageFromServer');
socket.on('messageToServer', (dataFromClient) => {
connectedUsers[dataFromClient.username] = socket;
});
socket.on('join', ({ username, room }) => {
socket.join(room);
socket.emit('message', 'Welcome!');
socket.broadcast
.to(room)
.emit('message', `${username} has joined the room!`);
});
socket.on('messageRoom', ({ username, room, message }) => {
socket.broadcast.to(room).emit('message', `${username}: ${message}`);
});
socket.on('call', ({ username, id }) => {
if (connectedUsers[username]) {
connectedUsers[username].emit('recieveCall', id);
console.log('online emitted');
} else {
socket.emit('userOff');
console.log('offline emitted');
}
});
socket.on('disconnect', () => {
socket.disconnect(true);
});
});
client:
import React, { useContext, useEffect } from 'react';
import socketIOClient from 'socket.io-client';
import StateContext from '../StateContext';
import DispatchContext from '../DispatchContext';
const endpoint = 'http://localhost:5000';
const Socket = () => {
const appState = useContext(StateContext);
const appDispatch = useContext(DispatchContext);
const socket = socketIOClient(endpoint);
useEffect(() => {
socket.on('messageFromServer', () => {
socket.emit('messageToServer', { username: appState.username });
});
socket.on('userOff', () => {
console.log('user offline');
});
socket.on('recieveCall', (id) => {
console.log('recieve call');
});
socket.on('mailNotification', () => {
document.getElementById('notifyMail').classList.add('notify');
});
socket.on('boardsNotification', () => {
document.getElementById('notifyBoards').classList.add('notify');
});
}, []);
return null;
};
export default Socket;
did you try call socket.open first
https://socket.io/docs/client-api/#socketopen
and listen the connect event
https://socket.io/docs/client-api/#Event-%E2%80%98connect%E2%80%99

send socket request from one component to different component

There is a two different component. One is patient and another is Doctor. The doctor can be on any page. When patient fires join event from his page to a particular doctor(e.g abc), the node server will then catch that event and will send JoinAccept event to 'abc doctor'. Following doctor should get notification that following patient wants to connect with you.
To achieve this, I could able to send 'Join' event up to server but could not able to listen JoinAccept event on doctor's end.
This is what I have done
context.js
import io from "socket.io-client";
import { SOCKET_URL } from "constants/url";
const SocketContext = React.createContext();
const SocketProvider = ({ children }) => {
const [socketClient, setSocketClient] = React.useState();
const [socketUpdated, setSocketUpdated] = React.useState(false);
React.useEffect(() => {
const socket = io(SOCKET_URL);
setSocketClient(socket);
return () => {
io.disconnect();
};
}, []);
React.useEffect(() => {
console.log("socketClient", socketClient);
if (socketClient) {
const tokenData =
!!localStorage.token &&
JSON.parse(atob(localStorage.token.split(".")[1]));
if (tokenData.user) {
console.log("user", tokenData.user);
socketClient.emit("clientData", tokenData.user);
socketClient.on("connected", msg => {
console.log("connected");
setSocketUpdated(true);
});
// setSocketUpdated(true);
}
}
}, [socketClient]);
console.log("socket updated", socketUpdated);
return (
<>
<SocketContext.Provider value={{ socket: socketClient, socketUpdated }}>
{children}
</SocketContext.Provider>
</>
);
};
export { SocketContext, SocketProvider };
Patient.js (it will fire Join event)
const Patient = () => {
const { socket } = React.useContext(SocketContext);
React.useEffect(() => {
const data = {
to: "abcdoctor#gmail.com",
from: "patient#gmail.com",
message: "Join a call"
};
socket.emit("Join", data);
}, [socket]);
return (
<div>
<h1>Patient</h1>
</div>
);
};
Doctor.js
const DoctorParentComponent = () => {
return (
<>
<SocketProvider>
<h1>Navbar</h1>
<DoctorRoutes />
</SocketProvider>
</>
);
};
DoctorPage.js
const DoctorPage = () => {
const [msg, setMessage] = React.useState("");
const { socket, socketUpdated } = React.useContext(SocketContext);
console.log("Doctor socket", socket, socketUpdated);
React.useEffect(() => {
console.log("socket", socket);
if (socket !== undefined) {
console.log("socket join", socket);
socket.on("JoinAccept", message => {
debugger;
console.log("message", message);
setMessage(message);
});
}
}, [socket]);
return (
<div>
<h1>Doctor </h1>
</div>
);
};
server.js
io.of("/sockets").on("connection", socket => {
console.log("socket connection is made!!!", socket.id);
socket.on("clientData", clientData => {
console.log(clientData, "CLEITN DATA");
socket.emit("connected", "connected");
});
socket.on("Join", data => {
const msg = {
message: "I am joining"
};
console.log("socket", socket.id);
console.log("I am Joining", data);
socket.broadcast.to(data.to).emit("JoinAccept", msg);
});
}
In my case the server gets Join event from patients and then sends event to requested doctor but doctor page is unresponsive. I mean doctor page does not listens the changes i.e it could not listens socket event JoinAccept so that it can join patient's request.
UPDATE
changing
socket.on("Join", data => {
const msg = {
message: "I am joining"
};
console.log("socket", socket.id);
console.log("I am Joining", data);
socket.broadcast.to(data.to).emit("JoinAccept", msg);
});
to following works
socket.on("Join", data => {
const msg = {
message: "I am joining"
};
console.log("socket", socket.id);
console.log("I am Joining", data);
io.of("/sockets").emit("JoinAccept", msg)
});
But I want to emit 'JoinAccept' event only for a particular doctor which patient has requested for from join events.
On DoctorPage.js socket is an object, if you put this object as the only variable in the dependency array it's going to run as a loop.
Try switching to the socket id instead, so:
React.useEffect(() => {
console.log("socket", socket);
if (socket !== undefined) {
console.log("socket join", socket);
socket.on("JoinAccept", message => {
debugger;
console.log("message", message);
setMessage(message);
});
}
}, [socket.id]);

Categories

Resources