Trí Phan
Trí Phan

Reputation: 1193

Socket IO emit events twice

Situation:

I'm setting up an express server with socket.io and a client using ReactJS. This is my server:

//... another requirement
const socket = require("./socket");

const app = express();
const server = http.createServer(app);
const port = process.env.PORT || 3000;

//... some parsers ...

routes(app);
socket(server);

server.listen(port);
console.log("Server started on: " + port);

And this is my socket:

const socketIO = require("socket.io");

const socket = server => {
  const io = socketIO(server);
  const chat = io.of("/chat");
  chat.on("connection", client => {
    console.log("User started chat!");
    client.on("disconnect", () => {
      console.log("User left chat!");
    });

    client.on("sendMessage", msg => {
      const { id, text } = msg;
      console.log(id + " says: " + text);
      client.broadcast.emit("incomingMessage", {
        //... response data
      });
    });
  });
};

In the client, the Chat Page component will handle sending message to socket and receiving message from socket:

//_ChatBox in Chat Page
const _ChatBox = (/*props*/) => {
  const chat = io("http://localhost" + ":3000/chat");

  chat.on("incomingMessage", message => {
    console.log("Receive message");
    //...handle income message
  });

  const sendMessageToSocket = (data) => {
    chat.emit("sendMessage", data);
  };

  return ( /*JSX*/);
};

Problem:

Everytime a user accesses Chat Page, socket will receive connect event and log User started chat! to console once, but actually, it has logged twice, that means the socket has handled the connect event twice.

And when _ChatBox receives message, it will log Receive message and display that message, but what it has done is log that twice and display the message twice, that also means the socket has emitted the incomeMessage event twice.

What i want: I want the socket only handles those events once.

Upvotes: 3

Views: 17444

Answers (6)

Md Arif Khan
Md Arif Khan

Reputation: 49

Add websocket.off("eventname") in return of useEffect

 useEffect(() => {
   ws.on("room-created", enterRoom);

   return () => {
     ws.off("room-created");
   };
 }, []);

Upvotes: 0

flyingace
flyingace

Reputation: 1887

There are a few solutions here already that contain parts of a complete answer and while this answer may already be available in pieces I think it is helpful to have it all in one place.

As others have said, you need to turn your listener on inside of a useEffect hook but also to include a return statement in which your listener is turned off. This way if/when the component reloads it doesn't add an additional instance of the listener.

useEffect(() => {
  socket.on('event-name', eventHandlingFunction);

  return () => {
    socket.off('event-name', eventHandlingFunction);
  };
}, [eventHandlingFunction]);

Incidentally you can also use the above structure for socket.onAny and socket.offAny, though you would, of course, not include the name of a single event socket was listening for.

Upvotes: 0

Cattynip
Cattynip

Reputation: 11

I had the same problem, and I solved it.

 return() => {
     socket.off("whatever_you_named").off();
 }

As you see here, you have to use the useEffect function that React is providing. Next, you have to attach the below code inside of your useEffect function to prevent receiving the same message twice. If you have 4 listening methods, you would have to attach that codes 4 times to prevent that problem for each method.

Upvotes: 0

Ginniped
Ginniped

Reputation: 13

I had a similar problem. I solved it by writing inside my functional component a useEffect that opens socket onMount (and onUpdate) but CLOSES SOCKET ON UNMOUNT.

useEffect(()=>{
    const socket = io("http://localhost:3000")
    socket.on(userId, (arg) => {
        //stuff
    }); 
    return () => socket.emit('end'); //Close socket on UNMOUNT
})

Upvotes: 0

Pawan Bishnoi
Pawan Bishnoi

Reputation: 2127

Create socket inside index.js file and pass as a prop to the root app component like this :-

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import socketIOClient from "socket.io-client"
var socket = socketIOClient('http://localhost:9000');
ReactDOM.render(
  <React.StrictMode>
     <App socket={socket} />
  </React.StrictMode>,
document.getElementById('root')
);

Now Use This socket to emit events like this:-

import React from "react"
import './App.css';
class App extends React.Component {
componentDidMount() {
   this.props.socket.emit('test')
}
render() {
  return (
     <div>
        hello
    </div>
  );
}
}
export default App;

Upvotes: 2

Will Jenkins
Will Jenkins

Reputation: 9787

It looks like your component is rendering twice, so calling const chat = io("http://localhost" + ":3000/chat"); twice, giving two connection messages.

You can use a useEffect() hook to only connect on the first render. Passing in an empty array as the second argument means the effect will only be called once:

const _ChatBox = (/*props*/) => {

  useEffect(()=>{
     const chat = io("http://localhost" + ":3000/chat");

    chat.on("incomingMessage", message => {
      console.log("Receive message");
      //...handle income message
     });

     const sendMessageToSocket = (data) => {
      chat.emit("sendMessage", data);
     };
   }, []);


  return ( /*JSX*/);
};

Upvotes: 12

Related Questions