Using Socket.io in React

December 11th, 20245 min read42 views

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.

Table of contents#

  1. Prerequisites
    1. Socket.io server
    2. Installing socket.io-client
  2. What we will be using
  3. SocketContext
  4. useSocket hook
  5. SocketProvider component
  6. Wrapping the app with the provider
    1. (Optional) Adding a loading screen
  7. Usage of the hook
  8. Honorable mention
  9. That's it!

Prerequisites#

Socket.io server#

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.

Installing socket.io-client#

If you don't have socket.io-client installed yet, run the following command:

sh
npm install socket.io-client

What we will be using#

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

SocketContext#

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.

useSocket hook#

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;
}

SocketProvider component#

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>
  );
}

Wrapping the app with the 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.

(Optional) Adding a loading screen#

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>
  );
}

Usage of the hook#

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.

Honorable mention#

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.

That's it!#

You've now set up a proper way to use Socket.io in React. I hope this guide has been helpful.

Enjoyed the article?