Using Socket.IO with React
The single most common React mistake with Socket.IO is creating a new connection inside a component's render body or inside useEffect with a dependency array that changes often, which opens a fresh socket on every re-render and leaks connections; the correct pattern is to create the socket once, outside any component (a module-level singleton) or inside a useEffect with an empty dependency array [] that returns a cleanup function calling socket.disconnect(), ensuring exactly one connection per component mount, not per render.
Cricket analogy: It's like a broadcaster accidentally opening a brand-new satellite uplink every time the camera angle changes instead of once per session — you'd end up paying for and juggling dozens of redundant feeds instead of one stable connection reused throughout the match.
A Custom Hook and Context Provider Pattern
Once the singleton connection concern is solved, the cleanest React architecture wraps the socket in a SocketProvider using createContext, exposing the socket instance (and derived state like isConnected) to the component tree, so individual components use a useSocket() hook to access it rather than importing the raw socket module everywhere, which keeps event subscriptions colocated with the components that care about them; each component that listens for an event should register its socket.on inside its own useEffect and clean up with socket.off in the effect's return function, since forgetting the off call is the second most common source of duplicate-handler bugs, especially with React StrictMode's intentional double-invoke in development.
Cricket analogy: It's like a team's shared dressing-room radio broadcast — every player (component) tunes in via their own earpiece (useEffect) rather than each player needing to physically plug into the broadcast van, and each player removes their earpiece (off) when they leave the huddle so they don't keep hearing calls meant for the field.
Handling Reconnection State in the UI
Socket.IO's client automatically attempts reconnection by default, but the UI should reflect this rather than silently failing, which means listening for connect, disconnect, and connect_error events to drive a small state machine (connecting | connected | reconnecting | disconnected) that a banner or toast component reads from context; it's also important to re-fetch or re-sync any state that might have changed while disconnected (like unread notification counts) inside the connect handler specifically when socket.recovered is false, since Socket.IO's connection state recovery feature can sometimes replay missed events automatically but should not be assumed to always succeed.
Cricket analogy: It's like a rain-delay indicator on a cricket broadcast — viewers see an explicit 'players off the field, play will resume shortly' banner rather than the feed just silently freezing, and once play resumes the broadcast explicitly re-syncs the scoreboard rather than assuming nothing changed.
// SocketProvider.jsx
import { createContext, useContext, useEffect, useRef, useState } from 'react';
import { io } from 'socket.io-client';
const SocketContext = createContext(null);
export function SocketProvider({ children, token }) {
const socketRef = useRef(null);
const [isConnected, setIsConnected] = useState(false);
if (!socketRef.current) {
socketRef.current = io(process.env.REACT_APP_SOCKET_URL, {
auth: { token },
autoConnect: false,
});
}
useEffect(() => {
const socket = socketRef.current;
socket.connect();
const onConnect = () => setIsConnected(true);
const onDisconnect = () => setIsConnected(false);
socket.on('connect', onConnect);
socket.on('disconnect', onDisconnect);
return () => {
socket.off('connect', onConnect);
socket.off('disconnect', onDisconnect);
socket.disconnect();
};
}, []);
return (
<SocketContext.Provider value={{ socket: socketRef.current, isConnected }}>
{children}
</SocketContext.Provider>
);
}
export const useSocket = () => useContext(SocketContext);Under React 18 StrictMode in development, effects mount, unmount, then mount again, which will call socket.disconnect() then immediately reconnect. This is expected and mirrors production behavior after any real remount — don't 'fix' it by removing the cleanup function, or you'll leak real connections in production.
- Create the Socket.IO connection once per component tree, not per render — use a ref, module singleton, or an empty-dependency
useEffect. - Always clean up with
socket.disconnect()(orsocket.offfor individual listeners) in the effect's return function. - A
SocketProvider+useSocket()context pattern colocates event subscriptions with the components that need them. - Register listeners in
useEffectand remove them withsocket.offto avoid duplicate handlers, especially under StrictMode's double-invoke. - Drive UI state (connecting/connected/reconnecting/disconnected) from
connect,disconnect, andconnect_errorevents rather than assuming silent success. - Re-sync state on reconnect, especially when
socket.recoveredis false, since automatic connection state recovery isn't guaranteed. - React StrictMode's mount-unmount-mount cycle in development is expected behavior, not a bug in your cleanup logic.
Practice what you learned
1. What is the most common mistake when using Socket.IO inside a React component?
2. What should a component do in the cleanup function of a `useEffect` that registers a `socket.on` listener?
3. Why does React 18 StrictMode cause a socket to disconnect and reconnect immediately in development?
4. What is the benefit of a `SocketProvider` + `useSocket()` context pattern?
Was this page helpful?
You May Also Like
Building a Chat Application
Learn how to design a real-time chat app with Socket.IO, covering rooms, broadcasting, message persistence, and delivery guarantees.
Real-Time Notifications
Design a reliable real-time notification system with Socket.IO using per-user rooms, namespaces, and durable fan-out for offline users.
Testing Socket.IO Apps
Write reliable integration and unit tests for Socket.IO features using real servers, real clients, and targeted mocking strategies.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics