socket io event fires multiple times - javascript

I have searched similar questions here but none of them work for me.
I know some people recommend not to use socket inside another event but I had no clue how to trigger socket whenever there is an event.
So I have initialized socket inside another event which is updated every time something happens. But socket connection repeats the previous result with every new update.
I tried initializing socket within componentDidMount lifecyle and it simply does not work.
class UploadComponent extends Component {
constructor (props) {
super(props);
this.state = {
endpoint: "http://localhost:3000",
}
this.uploadModal = this.uploadModal.bind(this);
}
uploadModal () {
update.on('success', file => {
let {endpoint} = this.state;
let socket = socketIOClient(endpoint, {transports: ['websocket', 'polling', 'flashsocket']});
socket.on('data', (mydata) => {
console.log(mydata) // <-- This gets fired multiple times.
})
})
}
// some more code //
}
I want to trigger socket whenever "update" event is fired without message duplication.

As sockets are emitting multiple times on Angular with nodejs happened the same with me for sockets, i tried by removing the socket listeners by this.socket.removeListener( "Your On Event" );,
This helped me solved the issue of multiple socket calls, try it, it may help !

Unless you can guarantee success is called only once, then you'll need to initialize the socket connection / event handler outside this function
class UploadComponent extends Component {
constructor (props) {
super(props);
const endpoint = "http://localhost:3000";
this.state = { endpoint };
this.uploadModal = this.uploadModal.bind(this);
this.socket = socketIOClient(endpoint, {transports: ['websocket', 'polling', 'flashsocket']});
this.socket.on('data', (mydata) => {
console.log(mydata)
})
}
uploadModal() {
update.on('success', file => {
// this.socket.emit perhaps?
})
}
}

As James have suggested I have put my socket logic in the constructor. But it was only being fired after my component remounts.
After looking at my nodejs server code I tried to replace
// some code //
io.on('connection', (client) => {
client.emit('data', {
image: true,
buffer: imageBuffer.toString('base64'),
fileName: meta.name
})
})
with this
// some code //
io.emit('data', {
image: true,
buffer: imageBuffer.toString('base64'),
fileName: meta.name
})
and it works!
Also I had to close socket in componentWillUnmount to avoid multiple repeated data.

Related

socket io to much rerendering in react

I'm using socketio and react. Im trying to set state for all users when one use changes it but it causes too much rerendering on site.
server.js
const app = require('express')
const http = require('http').createServer(app)
const io = require("socket.io")(http, {
cors: {
origin: "http://localhost:3000",
},
});
let subject = ''
io.on('connection', (socket) => {
socket.on('setSubject', (sbj) => {
subject = sbj
io.sockets.emit('getSubject',subject)
})
socket.on('changeHiddenPassword',hiddenPassword => {
socket.broadcast.emit('newHiddenPassword',hiddenPassword)
})
})
http.listen(4001,function(){
console.log('listening on port 4001')
})
front end
const [hiddenPassword,changeHiddenPassword] = useState([''])
useEffect(() => {
socket.emit('changeHiddenPassword',hiddenPassword)
}, [hiddenPassword]);
socket.on('newHiddenPassword',newHiddenPassword => {
changeHiddenPassword(newHiddenPassword)
})
I dont think rest of the code will help you somehow but if it would i can provide it to you. What i'm trying to do is when someone clicks on specific element on my site i want my hiddenPasword state to change for all connected users. I know that what i'm doing is wrong,beacouse when the state changes for one user useEffect is passing and setting state to everyone else and their useEffect are passing and setting state to everyone else and so on... I want to know how to do it properly and i cant figure out how.
so when you change hiddenPassword you are emitting changeHiddenPassword event, this sends back newHiddenPassword event which changes the hiddenPassword and this continues infinitely.
Try modifying newHiddenPassword handler like this, so it's not changing the state (and triggering useEffect) when the received password is already up to date
socket.on('newHiddenPassword',newHiddenPassword => {
if (newHiddenPassword !== hiddenPassword) {
changeHiddenPassword(newHiddenPassword);
}
});

How to use socket io inside class

I'm trying to create game using socket io. I want to implement socket inside server.js but I would like to keep events inside Room.js.:
server.js
const io = require('socket.io').listen(server)
io.sockets.on('connection', socket => {
socket.on('join room', data => {
let room = new Room(data.id, socket)
socket.join(`room${data.id}`)
}
}
Room.js
class Room{
constuctor(id, socket){
this.id = id,
this.socket = socket,
this.handlerOfEvents()
}
handlerOfEvents() {
this.socket.on('new player connected', data => {
console.log('New player connected!')
}
}
}
I tried do as above but it doesn't work:
\node_modules\has-binary2\index.js:30
function hasBinary (obj) {
^
RangeError: Maximum call stack size exceeded
Is there any solution to do sth like this?
Or maybe there is another perfect way to implement events for particular room.
This problem occurs when I try assign socket to this.socket but when I put it directly as argument like below:
class Room{
constuctor(id, socket){
this.id = id,
//this.socket = socket,
this.handlerOfEvents(socket)
}
handlerOfEvents(socket) {
socket.on('new player connected', data => {
console.log('New player connected!')
}
}
}
then just invoke this method in server.js
socket.on('joined new player', data => {
...
room.handlerOfEvents(socket)
}
so, this solution is working for now. But if this is proper way?
Issue from link in comments didn't solve my problem unfortunately.
Thanks for helping!

Different behaviour when calling emit from class methods

I am building an app with socket.io and typescript. As always I have created server and client but now I'm facing weird issue with my server code. My server is listening on 'connection' event and as callback creates new class instance and invokes his onConnect method. In this function it invokes another method - 'bindHandlers'. In this function socket listens to his events.
And this is my problem: if i pass callback to 'draw' event as an anonymous function it works as expected, but if i use my class method it sends events back to to the client instead of broadcasting it. I want to make my code more modular and this issue is blocking me for now.
main file:
io.on("connection", SocketService.createInstance(db).onConnect);
simplified socket file:
export class SocketService {
private socket: Socket | null = null;
constructor(private db: DB) {}
static createInstance = (db: DB) => {
return new SocketService(db);
};
onConnect = (socket: Socket) => {
this.socket = socket;
const username = socket.handshake.query.user;
console.log(`${username} connected ${socket.id}`);
this.bindHandlers(socket);
};
private bindHandlers = (socket: Socket) => {
if (!this.socket) return console.log("socket is undefined");
socket.on("draw", this.onDraw);
// if I swap with code below it works properly
// socket.on("draw", data => {
// socket.broadcast.emit("draw", data);
// });
};
private onDraw = (data: DrawingPoint) => {
const username = this.socket!.handshake.query.user;
const { group } = data;
this.socket!.broadcast.emit("draw", data);
};
The reason why it is working with:
socket.on("draw", data => {
socket.broadcast.emit("draw", data);
});
is because you are using arrow function which for context (this) will have surrounding context, but when defining an event handler such as: socket.on("draw", this.onDraw); context will not be anymore instance of SocketService. Play with it a little bit and debug it to see what will be the context in a case when you are calling it with that.
One solution would be to set the context explicitly such as:
socket.on("draw", this.onDraw.bind(this));
Keep in mind that context of the method/function in JS depends on how method was called.

Apollo subscriptions - handling WS disconnects with subscribeToMore

I've been looking for a way to handle web socket disconnects in my React app with Apollo subscriptions and have not found a way to do so. The other examples I see in the apollo documentation show the below method for catching a reconnect:
const wsClient = process.browser ? new SubscriptionClient(WSendpoint, {
reconnect: true,
}) : null;
const wsLink = process.browser ? new WebSocketLink(wsClient) : null;
if (process.browser) {
wsLink.subscriptionClient.on(
'reconnected',
() => {
console.log('reconnected')
},
)
}
There are two issues with the above method:
is that is does not catch when the user disconnects from their internet (only from when the server restarts for whatever reason)
that the reconnect is triggered outside of my React apps components.
What I would like to be able to do is to is reload my "chat" component if the user either disconnects from their internet or if my express server goes down for any reason. For this to happen I would need my chat component to completely reload which i'm not sure would be possible from outside my component tree.
Is there a method in the Query or Subscription Apollo components to be able to capture this event and handle it accordingly from the component?
There are a few ways I can think of to handle these cases but none of them are a one-shot solution, each case needs to be handled independently.
Setup a online/offline listener (ref)
Setup an Apollo middleware to handle network errors from your server (ref)
Create a variable in your store, isOnline for example, which can hold a global reference of your app's state. Whenever the above two methods trigger, you could update the value of isOnline
Finally, to bundle all of it together. Create a react HOC which uses isOnline to handle the network state for each component. This can be used to handle network error messages, refresh components once network is restored.
You can use SubscriptionClient callbacks from subscriptions-transport-ws, like this:
const ws = require("ws");
const { SubscriptionClient } = require("subscriptions-transport-ws");
const { WebSocketLink } = require("apollo-link-ws");
const { ApolloClient } = require("apollo-client");
const { InMemoryCache } = require("apollo-cache-inmemory");
const subClient = new SubscriptionClient(
'ws://localhost:4000/graphql',
{ reconnect: true },
ws
);
subClient.onConnected(() => { console.log("onConnected") });
subClient.onReconnected(() => { console.log("onReconnected") });
subClient.onReconnecting(() => { console.log("onReconnecting") });
subClient.onDisconnected(() => { console.log("onDisconnected") });
subClient.onError(error => { console.log("onError", error.message) });
const wsLink = new WebSocketLink(subClient);
const client = new ApolloClient({
link: wsLink,
cache: new InMemoryCache()
});
I'm using this for Node.js, but it will probably work for React too.

Flux and WebSockets

I'm using Flux and WebSocket in my Reactjs application and during implementation I've encountered some problems.
Questions:
Assuming I have a set of a set of actioncreators and a store for managing the WebSocket connection, and that the connection is started in a actioncreator (open(token)), where should I put my conn.emit's and how do I get other actions access to my connection object so that they can send data to the backend?
Do I have to pass it as an argument to the actions that are called in the views (eg. TodoActions.create(conn, todo)) or is there a smarter way?
Current code is here
I'm using ES6 classes.
If I have omitted anything necessary in the gist, please let me know.
EDIT:
This is what I have concocted so far based on glortho's answer:
import { WS_URL } from "./constants/ws";
import WSActions from "./actions/ws";
class WSClient {
constructor() {
this.conn = null;
}
open(token) {
this.conn = new WebSocket(WS_URL + "?access_token=" + token);
this.conn.onopen = WSActions.onOpen;
this.conn.onclose = WSActions.onClose;
this.conn.onerror = WSActions.onError;
this.conn.addEventListener("action", (payload) => {
WSActions.onAction(payload);
});
}
close() {
this.conn.close();
}
send(msg) {
return this.conn.send(msg);
}
}
export default new WSClient();
You should have a singleton module (not a store or an action creator) that handles opening the socket and directing traffic through. Then any action creator that needs to send/receive data via the socket just requires the module and makes use of its generic methods.
Here's a quick and dirty untested example (assuming you're using CommonJS):
SocketUtils.js:
var SocketActions = require('../actions/SocketActions.js');
var socket = new WebSocket(...);
// your stores will be listening for these dispatches
socket.onmessage = SocketActions.onMessage;
socket.onerror = SocketActions.onError;
module.exports = {
send: function(msg) {
return socket.send(msg);
}
};
MyActionCreator.js
var SocketUtils = require('../lib/SocketUtils.js');
var MyActionCreator = {
onSendStuff: function(msg) {
SocketUtils.send(msg);
// maybe dispatch something here, though the incoming data dispatch will come via SocketActions.onMessage
}
};
Of course, in reality you'll be doing better and different things, but this gives you a sense of how you might structure it.

Categories

Resources