Using socket.io-client in React , isn't that straightforward. This post, will show you how to use socket.io-client in React the right way, without any issues.
Prerequisites
Socket.io server
Installing socket.io-client
What we will be using
SocketContext
useSocket hook
SocketProvider component
Wrapping the app with the provider
(Optional) Adding a loading screen
Usage of the hook
Honorable mention
That's it!
This guide assumes you have a socket.io server already setup and running. If you don't have one, check out the socket.io documentation .
If you don't have socket.io-client installed yet, run the following command:
npm pnpm bun yarn
sh npm install socket.io-client
React Context is the key element, which allows us to pass information from a parent component to any component in the tree blow it, no matter how deep. Passing Data Deeply with Context
Let's start by creating a SocketContext
variable.
ts import { createContext } from "react" ;
import { Socket } from "socket.io-client" ;
type SocketContextType = {
socket : Socket | null ;
isConnected : boolean ;
isLoading : boolean ;
};
export const SocketContext = createContext < SocketContextType >({
socket: null ,
isConnected: false ,
isLoading: true ,
});
This component won't just return the socket, it will also return booleans to indicate whether the socket is connected and whether the socket is loading i.e. whether it's still trying to connect to the server.
The next step is to create a custom hook that will be used to access the socket context.
tsx import { SocketContext } from "path/to/socket-context" ;
import { useContext } from "react" ;
export function useSocket () {
const context = useContext (SocketContext);
if (context === undefined ) {
throw new Error ( "useSocket must be used within a SocketProvider." );
}
return context;
}
Now, let's create a SocketProvider
component. This component will wrap our app and provide the socket context to all the components below it. Make sure to read through the comments to understand what's going on.
tsx import { SocketContext } from "path/to/socket-context" ;
import { useEffect, useState } from "react" ;
import { Socket, io } from "socket.io-client" ;
// The URL of the socket server, change this to your own, e.g. .env variable
const ioServerUrl = "http://localhost:3000/" ;
// Create socket connection without auto connecting to the server
function createSocketConnection () {
return io (ioServerUrl, {
autoConnect: false ,
});
}
export function SocketProvider ({ children } : { children : React . ReactNode }) {
const [ socket , setSocket ] = useState < Socket | null >( null );
const [ isConnected , setIsConnected ] = useState ( false );
const [ isLoading , setIsLoading ] = useState ( true );
useEffect (() => {
const newSocket = createSocketConnection ();
// Handles the socket connection event
function handleConnect () {
console. log ( "Socket connected" , newSocket.id);
setSocket (newSocket);
setIsConnected ( true );
setIsLoading ( false );
}
// Handles the socket disconnection event, i.e. if the connection is lost
function handleDisconnect () {
console. log ( "Socket disconnected" );
setIsConnected ( false );
setIsLoading ( false );
}
// Handles the socket connection error event, e.g. the server is down
function handleConnectError () {
console. error ( "Socket connection error" , error);
setIsLoading ( false );
}
newSocket. on ( "connect" , handleConnect);
newSocket. on ( "disconnect" , handleDisconnect);
newSocket. on ( "connect_error" , handleConnectError);
// Connects to the server
newSocket. connect ();
// Cleans up the event listeners and disconnects from the server
return () => {
newSocket. off ( "connect" , handleConnect);
newSocket. off ( "disconnect" , handleDisconnect);
newSocket. off ( "connect_error" , handleConnectError);
newSocket. disconnect ();
};
}, []);
return (
< SocketContext.Provider value = {{ socket, isConnected, isLoading }}>
{children}
</ SocketContext.Provider >
);
}
Finally, let's wrap the components for which we want to access the socket context with the provider.
tsx import { SocketProvider } from "path/to/socket-provider" ;
export function App ({ children } : { children : React . ReactNode }) {
return < SocketProvider >{children}</ SocketProvider >;
}
The way which you need to wrap your app may be a bit different depending on the framework you're using.
Connections to the server can take a while, so it's a good idea to add a loading screen. Keep in mind that this may not be the best way to do this as it will block the user from interacting with the app until the connection is established.
tsx import { SocketProvider } from "path/to/socket-provider" ;
import { useSocket } from "path/to/use-socket" ;
function SocketLoader ({ children } : { children : React . ReactNode }) {
const { isLoading } = useSocket ();
if (isLoading) {
return < p >Connecting to the server...</ p >;
}
return <>{children}</>;
}
export function App ({ children } : { children : React . ReactNode }) {
return (
< SocketProvider >
< SocketLoader >{children}</ SocketLoader >
</ SocketProvider >
);
}
tsx import { useSocket } from "path/to/use-socket" ;
// This component should be used inside of a SocketProvider
export function ChildComponent () {
const { socket } = useSocket ();
return < p >Connected as {socket?.id}</>;
}
This example will simply return the connected socket ID. But at this point you can basically do whatever you want with the socket .
If you intend to use regular WebSockets instead of Socket.io, you can use the react-user-websocket library, it also supports Socket.io out of the box and has several useful features .
You've now set up a proper way to use Socket.io in React. I hope this guide has been helpful.
Enjoyed the article?