Background
The following is my first blog to achieve 8k+ view and 1st in Google SERP rank. However, it has some concerns and I decided to rewrite it.

How to prevent multiple socket connections and events in React
bravemaster619 ・ Mar 22 '20 ・ 2 min read
In this article, I used global socket
variable to manage socket events in a React app. Like the following:
// service/socket.js
export const socket = socketio.connect(SOCKET_URL);
// elsewhere
import {socket} from "service/socket";
However, fellow devs recommended to use React context API in their comments. And I began to think that using global variable is not a React-way either. (Although I believe that works too. Because socket does not change its state).
I updated my previous article, but I think it needs more explanation. So I decided to write an article that shows how to use React context API to manage one global socket instance.
1. Create Socket Context
We will use useContext hook to provide SocketContext to entire app.
Create a file in context/socket.js
:
import socketio from "socket.io-client";
import { SOCKET_URL } from "config";
export const socket = socketio.connect(SOCKET_URL);
export const SocketContext = React.createContext();
2. Use socket context and provide a value
Add SocketContext provider at the root of your project or at the largest scope where socket is used:
import {SocketContext, socket} from 'context/socket';
import Child from 'components/Child';
const App = () => {
return (
<SocketContext.Provider value={socket}>
<Child />
<Child />
...
</SocketContext.Provider
);
};
3. Now you can use socket in any child component
For example, in GrandChild
component, you can use socket like this:
import React, {useState, useContext, useCallback, useEffect} from 'react';
import {SocketContext} from 'context/socket';
const GrandChild = ({userId}) => {
const socket = useContext(SocketContext);
const [joined, setJoined] = useState(false);
const handleInviteAccepted = useCallback(() => {
setJoined(true);
}, []);
const handleJoinChat = useCallback(() => {
socket.emit("SEND_JOIN_REQUEST");
}, []);
useEffect(() => {
// as soon as the component is mounted, do the following tasks:
// emit USER_ONLINE event
socket.emit("USER_ONLINE", userId);
// subscribe to socket events
socket.on("JOIN_REQUEST_ACCEPTED", handleInviteAccepted);
return () => {
// before the component is destroyed
// unbind all event handlers used in this component
socket.off("JOIN_REQUEST_ACCEPTED", handleInviteAccepted);
};
}, [socket, userId, handleInviteAccepted]);
return (
<div>
{ joined ? (
<p>Click the button to send a request to join chat!</p>
) : (
<p>Congratulations! You are accepted to join chat!</p>
) }
<button onClick={handleJoinChat}>
Join Chat
</button>
</div>
);
};
OK, here are some explanations:
What is useContext
?
-
useContext
provides a React way to use global state - You can use context in any child component
- Context values are states. React notices their change and triggers re-render.
What is useCallback
? Why did you put every handlers inside useCallback
?
-
useCallback
prevents reassigning whenever there is state update - Functions will be reassigned only when elements in the second argument are updated
- Since we passed an empty array to second argument, functions are assigned for only once
- You may forget (or don't bother) to use
useCallback
. But you may face serious performance issues if there are many states and components in your project
What is useEffect
and that [socket]
array provided as the second argument?
The second argument is called dependency array. React will watch dependency array elements and whenever one of them is updated, the first argument function will be executed.
If you omit dependency array in
useEffect
, the function will be executed whenever there is a state update.If the dependency array is an empty array, the function will be executed only once.
In React functional component, you can write
componentDidMount
andcomponentWillUnmount
alternatives in the following way:
useEffect(() => {
// here is componentDidMount
return () => {
// here is componentWillUnmount
}
}, []);
- It is strongly recommended to put every state that's used in the first argument function to the dependency array.
BONUS
If you want to use JWT token to authenticate socket connections, you can do like the following:
const getSocket = () => {
const token = getAuthToken(); // get jwt token from local storage or cookie
if (token) {
return socketio.connect(SOCKET_URL, {
query: { token }
});
}
return socketio.connect(SOCKET_URL);
};
Then in socket server, you can get jwt token like the following:
import SocketIO from "socket.io";
const io = new SocketIO.Server(expressApp);
const jwtMiddleware = (socket, next) => {
const {token} = socket.handshake.query;
// verify token
};
io.use(jwtMiddleware);
Discussion (1)
Good example. Thanks