How to prevent multiple socket connections and events in React
Multiple socket connections and event bindings can be easily overlooked in SPAs and can cause headaches. I'd like to share my experience of preventing them. In this article, I will use socket.io
for Node.js backend and socket.io-client
for React frontend.
1. Connect only once
Create a dedicated file for socket connection. For example, create a file in service/socket.js
:
import io from "socket.io-client";
import { SOCKET_URL } from "config";
export const socket = io(SOCKET_URL);
You can import this socket instance in other React components whenever necessary:
import {socket} from "service/socket";
function MyComponent() => {
return(<div></div>)
}
In this way, you can ensure there will be only one socket instance.
2. Bind and emit events only once - put them in the correct place
Consider you're building a React component that connect to a socket server when the page is loaded and show welcome message if the connection is successful. The component will emit an HELLO_THERE
event to the server and the server will respond with warm WELCOME_FROM_SERVER
event. Where would you place socket event binding and emiting in your React code?
import {socket} from "service/socket";
// NOT HERE (1)
// socket.emit('HELLO_THERE');
function MyComponent() => {
// NOT HERE EITHER (2)
// socket.emit('HELLO_THERE');
const [connected, setConnected] = useState(false);
// IT IS HERE
useEffect(() => {
socket.emit('HELLO_THERE');
socket.on('WELCOME_FROM_SERVER', () => setConnected(true)});
}, []);
return (
<div>
{ connected ? (
<p>Welcome from server!</p>
) : (
<p>Not connected yet...</p>
) }
</div>
)
}
export default MyComponent
- (1) This will emit the event even if
MyComponent
is not used. - (2) This will emit the event whenever
MyComponent
is updated.
If you're using React classes, componentDidMount
will be the right place to use sockets. useEffect(() => {}, [])
is almost the same as componentDidMount
Of course, socket.emit
can go anywhere necessary in your code. However, socket.on
should be in componentDidMount
in most cases.
3. If you're unsure, there is always this hack
If you still cannot figure out why does your component stupidly bind the same listener for multiple times, or if you just want to make sure bind only one listener for one event, you can resort to socket.off
.
socket.off('MY_EVENT').on('MY_EVENT', () => doThisOnlyOnce());
Just like when you had to deal with multiple button events in jQuery
.
If you see any problem or have a better approach, please share your idea in comments. Happy coding!