f roy
f roy

Reputation: 33

io.on connection being triggered with every emit

every event handler i have on my socket.io calls the io.on conenction first. for example- i have a chat i made and everytime i send a message (emit it to all clients) it calls io.on connection and only then moves down to the event handler.

Server file

const express = require('express');
const app = express();
const http = require('http').createServer(app);
const io = require('socket.io')(http);


app.get('/', function (req, res) {
    res.sendFile(__dirname + '/index.html');
});
let clients = 0;
io.on('connection', function (socket) {
    clients++;
    console.log('a user connected');
    io.sockets.emit('User Connected', clients + ' clients connected');
    socket.on('disconnect', function () {
        clients--;
        console.log('user disconnected');
        io.sockets.emit('connected', clients + ' clients connected');
    });

    socket.on('msg sent', (msg) => {
        console.log(msg, "new msg emit");
        io.sockets.emit('msg sent', msg);
    });
    socket.on('user typing', (msg) => {
        io.sockets.emit('user typing', msg);
    });
});




http.listen(process.env.PORT || 8080, () => {
    console.log('server started')
});

Client side chat file


import React, {Component} from 'react';

import socketIOClient from 'socket.io-client';
import Button from "react-bootstrap/Button";


class Chat extends Component {
    constructor(props) {
        super(props);
        this.state = {endpoint: "localhost:8080", messages: [], newMsg: '', typing: '', connected: ''}
    }

    send = () => {
        const socket = socketIOClient(this.state.endpoint);
        socket.emit('msg sent', this.state.newMsg);
        this.setState({typing: ''});
    };

    sentMsg = (event) => {
        const socket = socketIOClient(this.state.endpoint);
        socket.emit('user typing', 'User Typing');
        this.setState({newMsg: event.target.value});
        console.log(this.state.newMsg);
    };


    componentDidMount() {
        const socket = socketIOClient(this.state.endpoint);
        socket.on('msg sent', (msg) => {
            console.log(msg, 'msg!!!');
            this.setState({messages: [...this.state.messages, msg], typing: ''});

        });
        socket.on('user typing', (msg) => {
            this.setState({typing: msg})
        });
        socket.on('User Connected', (msg) => {
            this.setState({connected: msg});
        })
    };

    renderMsg = () => {
        if (this.state.messages.length > 0) {
            return this.state.messages.map((msg) => {
                return (<div className="border border-primary rounded m-2 p-1">{msg}</div>)
            })
        }
    };

    render() {
        return (
            <div className="container-fluid">
                <div className="row chat p-2">
                    <div className="col-2 border-primary border m-1">
                        {this.state.connected}
                    </div>
                    <div className="col-9 border-danger border m-1 ">
                        {this.renderMsg()}
                        <br/>
                        {this.state.typing}
                    </div>
                </div>
                <div className="row">
                    <div className="fixed-bottom text-center">
                        <input type="text" className="col-6" onChange={this.sentMsg}/>
                        <Button className="m-1" onClick={() => {
                            this.send()
                        }}>Send</Button>
                    </div>

                </div>

            </div>
        )
    }

};

export default Chat;

would expect that only when there is a new connection the connected clients counter will update and add 1 but instead it does it with every event i send to it.

Upvotes: 2

Views: 934

Answers (2)

keysl
keysl

Reputation: 2167

Recently we have the same problem, Everytime the client changes a state, a new connection to the ws server will create, resulting to multiple websocket connection. What we have done is number 3 from @Chris Chen's answer

We use a web-worker for socket.io ws connection but you can implement this in plain react only. The idea is the same after all.

function WrapContainer(props) {
  const { provider, pairString } = props.state;
  useEffect(() => {
    const worker = new Worker('../../workers/blox_worker.js', {
      type: 'module',
    });
    worker.postMessage({
      marketPair: props.state.pairString,
      provider: props.state.provider,
    });

    // Handling of websockets data here which basically event listener from worker
    //...
    // ..
    return () => {
      worker.terminate();
    };
  }, []);
}

you can replace the worker.terminate with socket.disconnect(); the return part in the code means componentWillUnmount. This will ensure that when the component is destroyed the websocket will be killed also. If you don't kill the websocket, if you navigate back the component with websocket it will also create another connection. So adding this is vital if you want to have a consistent 1 websocket connection to each user.

EDIT:

Upon further investigating your code, it seems you're recreating the socket.io client every time you send a message. Client should only be one instance cause every time you call the socketIOClient new ws connection will also be created furthermore emitting should be done to that instance too. A more valid use case for your scenario is having a variable that will hold the socketio client.

import React, {Component} from 'react';

import socketIOClient from 'socket.io-client';
import Button from "react-bootstrap/Button";


class Chat extends Component {
    constructor(props) {
        super(props);
        this.state = {endpoint: "localhost:8080", messages: [], newMsg: '', typing: '', connected: ''}
    }

    send = () => {
        this.socket.emit('msg sent', this.state.newMsg);
        this.setState({typing: ''});
    }

    sentMsg = (event) => {
        this.socket.emit('user typing', 'User Typing');
        this.setState({newMsg: event.target.value});
        console.log(this.state.newMsg);
    }


    componentDidMount() {
        this.socket = socketIOClient(this.state.endpoint);
        this.socket.on('msg sent', (msg) => {
            console.log(msg, 'msg!!!');
            this.setState({messages: [...this.state.messages, msg], typing: ''});

        });
        this.socket.on('user typing', (msg) => {
            this.setState({typing: msg})
        });
        this.socket.on('User Connected', (msg) => {
            this.setState({connected: msg});
        })
    }

    renderMsg = () => {
        if (this.state.messages.length > 0) {
            return this.state.messages.map((msg) => {
                return (<div className="border border-primary rounded m-2 p-1">{msg}</div>);
            })
        }
    };

    render() {
        return (
            <div className="container-fluid">
                <div className="row chat p-2">
                    <div className="col-2 border-primary border m-1">
                        {this.state.connected}
                    </div>
                    <div className="col-9 border-danger border m-1 ">
                        {this.renderMsg()}
                        <br/>
                        {this.state.typing}
                    </div>
                </div>
                <div className="row">
                    <div className="fixed-bottom text-center">
                        <input type="text" className="col-6" onChange={this.sentMsg}/>
                        <Button className="m-1" onClick={() => {
                            this.send()
                        }}>Send</Button>
                    </div>

                </div>

            </div>
        )
    }

};

Upvotes: 1

Chris Chen
Chris Chen

Reputation: 1293

Your react component is re-rendering every time when a message is added which will trigger componentDidMount and create new socket.

Consider optimize your component by using the following methods:

  1. Use shouldComponentUpdate() https://reactjs.org/docs/react-component.html#shouldcomponentupdate
  2. Setup PureComponent https://reactjs.org/docs/react-api.html#reactpurecomponent
  3. Switch for functional component and setup code from componentDidMount into useEffect Hook with [] as second parameter https://reactjs.org/docs/hooks-reference.html#timing-of-effects. to make sure it only run once.

Upvotes: 1

Related Questions