Socket.on is not firing - javascript

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

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!

React does not update before another socket.io message

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 <></>;
}

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]);

How to get the log messages in the order I need?

How do I prevent Service running ... msg from getting logged first? I would like the messages inside testDBConnection fn. to be logged first instead. When DB is not running I would like the Looks like DB is not running msg to be kept getting logged and once the DB kicks in the DB connection has been established and Service running ... msgs should follow. I tried multiple things, but I was not able to come up with proper code. Thanks for your help.
index.js
import app from './config/express';
import config from './config/config';
import logger from './config/winston';
import { initDbConnection } from './server/db';
app.listen(config.port, () => {
initDbConnection();
logger.info(`Service running and listening on port ${config.port}`);
});
db.js
import knex from 'knex';
import config from '../config/config';
import logger from '../config/winston';
const { db } = config;
let pool;
const testDBConnection = (client) => {
const intervalId = setInterval(async () => {
try {
await client.select(1);
logger.info('DB connection has been established');
clearInterval(intervalId);
} catch (error) {
logger.error('Looks like DB is not running');
}
}, 2000);
};
export const initDbConnection = (mock) => {
if (mock) {
pool = knex({});
} else {
pool = knex({
client: 'pg',
version: '7.4.2',
connection: db,
debug: true
});
testDBConnection(pool);
}
};
export const getDb = () => pool;
You could use async/await for that.
import app from './config/express';
import config from './config/config';
import logger from './config/winston';
import { initDbConnection } from './server/db';
app.listen(config.port, async () => {
await initDbConnection();
logger.info(`Service running and listening on port ${config.port}`);
});
db.js:
import knex from 'knex';
import config from '../config/config';
import logger from '../config/winston';
const { db } = config;
let pool, connected;
const testDBConnection = (client) => {
return new Promise(resolve => {
const intervalId = setInterval(async () => {
try {
await client.select(1);
if (connected) {
return;
}
connected = true;
logger.info('DB connection has been established');
clearInterval(intervalId);
resolve('success');
} catch (error) {
logger.error('Looks like DB is not running');
}
}, 2000);
});
};
export const initDbConnection = (mock) => {
if (mock) {
pool = knex({});
} else {
pool = knex({
client: 'pg',
version: '7.4.2',
connection: db,
debug: true
});
return testDBConnection(pool);
}
};
export const getDb = () => pool;
This way, the logger inside the app.listen cb won't be called until the initDbConnection is resolved. Another way would be to just use the promise then.

Render User Count With React / Websockets

I have a server file which sends data with Websockets here. As you see, I have a counter which I defined as clientCount and I want to increase/decrease it based on how many users are connected to my host. So far I'm able to show the connections in my console but not in my React App.
const wss = new SocketServer({ server });
let clientCount = 0;
wss.on('connection', (ws) => {
console.log('Client connected');
clientCount++;
console.log(clientCount);
ws.on('message', (message) => {
console.log('Message from client', message);
let parsedMessage = JSON.parse(message);
parsedMessage.id = uuid();
console.log(message);
wss.clients.forEach(function each(client) {
if (client.readyState === ws.OPEN) {
client.send(JSON.stringify(parsedMessage));
}
});
});
ws.on('close', () => {
console.log('Client disconnected');
clientCount--;
});
});
Here in my React App, under my componentDidMount() I'm configuring my state to display in my console with the username/conent but that's irrelevant information.
In my render call inside a paragraph tag, I want to display the amount of users online in that field and updating every time they connect to the host, and disconnect. How do I display that information?
componentDidMount() {
this.socket = new WebSocket("ws://localhost:3001")
this.socket.onmessage = (event) => {
let message = JSON.parse(event.data)
this.setState({
messages: this.state.messages.concat([{
username: message.username,
content: message.content,
id: message.id
}])
})
console.log(JSON.parse(event.data));
}
}
render() {
const {currentUser, messages} = this.state
return (
<div>
<nav className="navbar">
Chatty
<p>RENDER USER COUNT HERE</p>
</nav>
<MessageList messages={messages} />
<ChatBar userProp={currentUser.name} messages={messages} submitMessage={this.newMessage}/>
</div>
);
}
}
You can make clientCount Observable. And then subscribe its changes in your react component. Every time clientCount changes, use setState to tigger a view update.
like this
class YourComp extends Component {
constructor(props) {
super(props)
this.state = { count: 0 }
}
onCountChange = (newCount) => {
this.setState({ count: newCount})
}
componentDidMount() {
clientCountObservable.subscribe(this.onCountChange)
}
componentWillUnmount() {
clientCountObservable.unsubscribe(this.onCountChange)
}
}
a simple Observable would be like
class Observable {
constructor(value) {
this.value = value
this.subscribers = []
}
subscribe(subscriber) {
this.subscribers.push(subscriber)
}
unsubscribe(subscriber) {
const index = this.subscribers.indexOf(subscriber)
if (index > -1) {
this.subscribers.splice(index, 1)
}
}
setValue(value) {
this.value = value
this.subscribers.forEach(sb => sb(value))
}
getValue() {
return this.value
}
}
You can create your own observable class, or use some other npm package.

Categories

Resources