Render User Count With React / Websockets - javascript

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.

Related

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

why componentdidmount called two times

I have React Component in componentDidMount fetch data from the server. The issue is componentDidMount called twice also the API called twice. I have a view increment API like youtube video views increment twice in the database because of twice API calling.
class SingleVideoPlay extends React.Component {
constructor(props) {
super(props);
this.player = React.createRef();
}
state = {
autoPlay: true,
relatedVideos: [],
video: null,
user: null,
comments: [],
commentInput: {
value: '',
touch: false,
error: false
},
following: false,
tab: 'comments'
};
_Mounted = false;
componentDidMount() {
this._Mounted = true;
if (this._Mounted) {
const videoId = this.props.match.params.id;
this.getVideoDetails(videoId);
}
}
componentWillUnmount() {
this._Mounted = false;
try {
clearInterval(this.state.videoInterval);
this.props.videoEditUrl('');
} catch (error) {}
}
captureVideoTime = async () => {
const { video } = this.state;
const result = await updateWatchTime({
id: video._id,
time: 1
});
if (result.status === 200) {
const updateVideo = {
...video,
secondsWatched: video.secondsWatched + 1
};
this.setState({ video: updateVideo });
}
};
videoEnded = () => {
clearInterval(this.state.videoInterval);
};
videoPause = () => {
clearInterval(this.state.videoInterval);
};
loadVideo = () => {
clearInterval(this.state.videoInterval);
};
playingVideo = () => {
const interval = setInterval(this.captureVideoTime, 1000);
this.setState({ videoInterval: interval });
};
getVideoDetails = async (videoId) => {
const video = await getVideo(videoId);
if (video.status === 200) {
let response = video.data;
if (this.props.userId)
if (response.user._id === this.props.userId._id)
this.props.videoEditUrl(`/video/edit/${response.media._id}`);
this.setState({
relatedVideos: response.videos.docs,
video: response.media,
user: response.user
});
this.checkIsFollowing();
this.updateVideoStat(response.media._id);
}
};
updateVideoStat = async (id) => videoView(id);
checkIsFollowing = async () => {
const { userId } = this.props;
const { video } = this.state;
if (userId && video) {
const response = await isFollow({
follower: userId._id,
following: video._id
});
if (response) {
this.setState({ following: response.following });
}
}
};
addOrRemoveFollowing = async () => {
this.checkIsFollowing();
const { following, video } = this.state;
const { userId } = this.props;
if (userId) {
if (following) {
const response = await removeFollow({
follower: userId._id,
following: video._id
});
this.setState({ following: false });
} else {
const response = await addFollow({
follower: userId._id,
following: video._id
});
this.setState({ following: true });
}
}
};
submitCommentHandler = async (event) => {
const { userId } = this.props;
event.preventDefault();
if (userId) {
const result = await saveComment({
mediaId: this.state.video._id,
parentId: '0',
userID: userId._id,
userName: userId.username,
comment: this.state.commentInput.value
});
console.log(result);
if (result.status === 200) {
this.getVideoComments();
this.setState({ commentInput: { value: '', touch: false, error: false } });
}
}
};
render() {
const { autoPlay, relatedVideos, video, user, comments, commentInput, following, tab } = this.state;
const { userId } = this.props;
return (
<div className="container-fluid">
some coponents
</div>
);
}
}
const mapStateToProps = (state) => ({
userId: state.auth.user
});
export default connect(mapStateToProps, { videoEditUrl })(SingleVideoPlay);
I don't know why componentDidMount called two times alse it shows memmory lecage issue.
How to Fix it.
Multiple componentDidMount calls may be caused by using <React.StrictMode> around your component. After removing it double calls are gone.
This is intended behavior to help detect unexpected side effects. You can read more about it in the docs. It happens only in development environment, while in production componentDidMount is called only once even with <React.StrictMode>.
This was tested with React 18.1.0
I think the issue exists on the parent component that used SingleVideoPlay component. Probably that parent component caused SingleVideoPlay component rendered more than once.
Also, there is an issue on your code.
componentDidMount() {
this._Mounted = true;
if (this._Mounted) {
const videoId = this.props.match.params.id;
this.getVideoDetails(videoId);
}
}
Here, no need to check if this._Mounted, because it will always be true.
1.Install jQuery by
npm i jquery
import $ from 'jquery'
create your function or jwuery code after the export command or put at the end of the file

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

Web-socket connection getting disconnected after one handshake in node.js and react

I am trying to build a simple Chatroom where multiple users can post the messages.I have setup the node web socket and it's broadcasting for a single message, but it closes the connection immediately after that.
This includes react on the front-end and node.js on the backend.
For simplicity i am storing and retrieving all the messages in a json file.
I am using ws: a node.js websocket library to setup the socket connection.
In client side i am using browsers WebSocket instance and listening on the same port.
// In server.js (Backend)
const server = http.createServer(doOnRequest)
const WebSocket = require('ws')
const wss = new WebSocket.Server({ port: 1994 })
wss.on('connection', function connection(ws) {
ws.on('message', function incoming(data) {
wss.clients.forEach(function each(client) {
if (client !== ws && client.readyState === WebSocket.OPEN){
client.send(data);
}
});
});
ws.on('close', () => {
console.log('server conn closed');
setTimeout(() => {
console.log('closed by server console. log');
}, 2000);
})
});
function doOnRequest(request, response) {
// This functions handles all the get and post calls and stores
it in a file
}
const server = http.createServer(doOnRequest)
server.listen(3001);
// IN Chatroom.js ( UI )
const URL = 'ws://localhost:1994'
class Chatroom extends React.Component {
ws = new WebSocket(URL)
constructor(props) {
////// binding classes declaring state ////
}
componentDidMount() {
this.ws.onopen = () => {
console.log('connected')
}
this.ws.onmessage = evt => {
// on receiving a message, add it to the list of messages
let x = this.state.messageList;
x ? x.push(JSON.parse(evt.data)) : [JSON.parse(evt.data)];
this.setState({
messageList: x,
value: ''
});
}
this.ws.onclose = () => {
console.log('disconnected')
}
this.renderOlderMessages();
}
renderOlderMessages() {
// render older messages using GET request
}
addNewMessage(chatObj) {
// let res = a Post request (with Request body as chatObj).
res.then(() => {
this.ws.send(JSON.stringify(chatObj))
let x = this.state.messageList;
x ? x.push(chatObj) : [chatObj];
this.setState({
messageList: x,
value: ''
});
});
}
render() {
return (
<div>
/// render old messages ///
</div>
<div>
<form>
Input ---> New message ---> Submit
</form>
</div>
)
}
}

Client does not gets reconnected once the client leaves the room or disconnects

I am using Kurento client for video calling in a room. There is only two participants(local and remote) in a call. A client can leave the room but when a client leaves the room then the stream of that client is not shown to other client which is obvious but when again that same client wants to join in the room, the client does not gets connected because of which the other client wont see his/her stream to have video call.
Here is how i have done
import kurentoUtils from "kurento-utils";
import socketIOClient from "socket.io-client";
import {
createWebRTCPeer,
sendMessage,
createWebRTCScreenPeer
} from "./common";
const CONSTRAINTS = {
audio: true,
video: {
width: 640,
framerate: 15
}
};
class VideoRoom extends Component {
constructor(props) {
super(props);
this.state = {
startCall: false,
room: "",
clientJoined: false,
email: "",
isLoggedIn: false,
open: false,
mute: false
};
this.localStreamRef = React.createRef();
this.remoteStreamRef = React.createRef();
this.onIceCandidates = this.onIceCandidates.bind(this);
this.handleError = this.handleError.bind(this);
this.socket = null;
this.webRTC = null;
this.loginName = null;
}
handleError = (e)=> {
console.log(e);
}
onIceCandidates(candidate) {
sendMessage(
{
event: "iceCandidate",
data: candidate
},
this.socket
);
}
componentDidMount() {
this.socket = new socketIOClient("http://localhost:8443/sockets");
this.webRtcPeer = null;
this.webRtcScreenPeer = null;
const { state } = this.props.location;
if (state && state.interviewId) {
this.initiateSocket();
}
}
initiateSocket() {
const { interviewId } = this.props.location.state;
this.socket.emit("room:addUser", { interviewId, userEmail: this.state.email });
this.socket.on("ICE_CANDIDATE", async candidate => {
console.log("candidate in listener", candidate);
await this.webRTC.addIceCandidate(candidate);
});
this.socket.on("RTC:PEER", room => {
console.log("RTC:PEER", room, this.localStreamRef);
this.webRtcPeer = createWebRTCPeer(
{
localVideo: this.localStreamRef.current,
remoteVideo: this.remoteStreamRef.current,
onicecandidate: this.onIceCandidates
},
this.socket,
room
);
});
this.socket.on("client:joined", () => {
this.setState({ clientJoined: true });
});
this.socket.on("iceCandidate", (candidate) => {
console.log("GOT Candidate....");
this.webRtcPeer.addIceCandidate(candidate);
});
this.socket.on("answer", answer => {
console.log("GOT ANSWER....");
this.webRtcPeer.processAnswer(answer);
});
this.socket.on("remote:leave", () => {
console.log("LEAVE FROM REMOTE");
this.handleLeaveRoom(true);
this.setState({ clientJoined: false });
});
this.socket.on("ERROR", error => this.onError(error));
}
componentWillUnmount() {
this.socket.emit('end');
this.socket = null;
this.webRtcPeer && this.webRtcPeer.dispose();
this.webRtcPeer = null;
}
onError = error => console.error(error);
handleLeaveRoom = (remote = false) => {
if (remote) {
this.remoteStreamRef.current.srcObject = null;
} else if (
this.webRtcPeer !== null &&
this.webRtcPeer !== undefined &&
this.socket !== null
) {
this.localStreamRef.current.srcObject = null;
this.props.history.push("/interivew-id");
} else {
return ;
}
};
render() {
return (
<React.Fragment>
<Wrapper>
<Studio
{...this.state}
interviewer={this.localStreamRef}
interviewee={this.remoteStreamRef}
/>
<Controls
handleLeaveRoom={() => this.handleLeaveRoom()}
handleMute={() => this.handleMute()}
mute={this.state.mute}
handleScreenShare={this.handleScreenShare}
/>
</Wrapper>
</React.Fragment>
);
}
}
export default withRouter(VideoRoom);
server.js
socket.on("end", () => {
console.log(`closing socket in room:: ${socket.room}`);
// console.log(`socket end: room: ${room ? room : socket.room}`)
socket.disconnect(0);
});
socket.on('disconnect', () => {
console.log("Client disconnected from", socket.room);
let session = sessions[socket.room];
if(session){
session.removeClient(socket.id);
}
socket.broadcast.to(socket.room).emit('remote:leave', socket.id);
});
Here is the full code
https://gist.github.com/SanskarSans/76aee1ab4ccab7c02dc812019f1329e9
The leave room works but when trying to re join, the client does not gets connected and show his/her stream to remote client.

Categories

Resources