Reputation: 1193
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
Reputation: 49
Add websocket.off("eventname") in return of useEffect
useEffect(() => {
ws.on("room-created", enterRoom);
return () => {
ws.off("room-created");
};
}, []);
Upvotes: 0
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
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
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
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
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