javascriptreactjssocketsnext.jswebsocket

socketInstanceRef.current.readyState becomes 3 after reconnect and is not rendering other component


So basically I'm using a connect.ts a custom hook for leveraging a single socket connection by not making new connections each time whenever I need to send a message from anywhere from my app.

I have added logic for reconnection in here as well, and it is working perfectly fine and I'm exporting latest socket instance from hook.

This is connect.ts hook.

import { useWallet } from "@solana/wallet-adapter-react";
import React from "react";

const socketInstances: Record<string, WebSocket | null> = {};

export function useWebSocket() {
  const [initData, setInitData] = React.useState<any>(null);
  const socketInstanceRef = React.useRef<WebSocket | null>(null);
  const messageListenerRef = React.useRef<
    ((event: MessageEvent) => void) | null
  >(null);
  const { disconnect } = useWallet();
  // inital messages handing
  const handleWebSocketMessage = React.useCallback((event: MessageEvent) => {
    try {
      const data = JSON.parse(event.data);
      if (data && Object.keys(data).length > 0) {
        data.action === "someAction" && setInitData(data);
        if (data.description === "token not found in database") {
          disconnect();
          localStorage.clear();
        }
      }
    } catch (error) {
      console.error("Error parsing WebSocket message:", error);
    }
  }, []);
  // connection making
  const connectWebSocket = React.useCallback(() => {
    if (
      socketInstanceRef.current &&
      socketInstanceRef.current.readyState === WebSocket.CLOSED
    ) {
      socketInstanceRef.current = new WebSocket(
        "socketURL",
        "protocol"
      );
      socketInstanceRef.current.onopen = () => {
        console.log(
          `Connected to the WebSocket server for URL: socketURL`
        );
      };
      socketInstanceRef.current.onclose = () => {
        console.log(
          `Disconnected from the WebSocket server for URL: socketURL`
        );
        // Reconnect when the connection is closed
        setTimeout(connectWebSocket, 1000);
      };
      // Update the socketInstances object with the new instance
      socketInstances["socketURL"] = socketInstanceRef.current;
    }
  }, []);

  React.useEffect(() => {
    if (!socketInstances["socketURL"]) {
      // Create a new WebSocket instance and add event listeners
      socketInstanceRef.current = new WebSocket(
        "socketURL",
        "protocol"
      );
      socketInstanceRef.current.onopen = () => {
        console.log(
          `Connected to the WebSocket server for URL: socketURL`
        );
      };
      socketInstanceRef.current.onclose = () => {
        console.log(
          `Disconnected from the WebSocket server for URL: socketURL`
        );
        // Reconnect when the connection is closed
        setTimeout(connectWebSocket, 1000);
      };
      // Update the socketInstances object with the new instance
      socketInstances["socketURL"] = socketInstanceRef.current;
    } else {
      // Update the socketInstanceRef.current when the WebSocket reconnects
      socketInstanceRef.current = socketInstances["socketURL"];
    }

    if (messageListenerRef.current) {
      socketInstanceRef.current?.removeEventListener(
        "message",
        messageListenerRef.current
      );
    }

    messageListenerRef.current = handleWebSocketMessage;

    if (messageListenerRef.current) {
      socketInstanceRef.current?.addEventListener(
        "message",
        messageListenerRef.current
      );
    }

    return () => {
      if (messageListenerRef.current) {
        socketInstanceRef.current?.removeEventListener(
          "message",
          messageListenerRef.current
        );
      }
    };
  }, [handleWebSocketMessage, connectWebSocket]);

  return {
    socketInstance: socketInstanceRef.current,
    initData: initData,
  };
}

// Export the socketInstances object for access to existing socket instances
export { socketInstances };

So after that when I come to another component and use useEffect hook to get initial data related to that specific page.

  const socketData = useWebSocket();
  const socketInstance = socketData?.socketInstance;

  useEffect(() => {
    if (socketInstance?.readyState === 1 && connected) {
      socketInstance.send(
        JSON.stringify({
          action: "getserverinfo",
          token: user?.token,
          data: {
            user: {
              address: `${publicKey?.toBase58()}`,
              chainid: 0,
            },
          },
        })
      );

      socketInstance.onmessage = (event: { data: string }) => {
        if (JSON.parse(event.data).action === "getserverinfo") {
          setAddressToDeposit(JSON.parse(event.data).data?.server.address);
        }
      };
    }
  }, [publicKey, socketInstance, connected]);

It works fine as well but whenever I wait here in another page for socket to get disconnect for some reason and gets reconnect again according to connnect.ts reconnection logic. And I try to run a function which is sending message to same socket connection like that

  const handleDeposit = async () => {
    if (!publicKey) throw new WalletNotConnectedError();
    if (!coinsToDeposit) throw new Error("Please enter a valid amount");
    if (!addressToDeposit) throw new Error("Please refresh the page");
    if (socketInstance?.readyState !== 1)
      throw new Error(
        `Please connect to the server ${socketInstance}, ${connected}`
      );
    if (socketInstance?.readyState === 1 && connected) {
      transferUSDC(coinsToDeposit).then((signature) => {
        console.log(signature);
        if (signature) {
          socketInstance?.send(
            JSON.stringify({
              action: "deposit",
              token: user?.token,
              data: {
                user: {
                  address: `${publicKey?.toBase58()}`,
                  chainid: 0,
                },
                amount: coinsToDeposit * 1000000,
                signature,
              },
            })
          );
        }
      });
    } else {
      console.log("socketInstance", socketInstance);
    }
  };

It doesn't send message and If I try to know the readyState it is 3, but initially when the useEffect on top of it ran it was 1 after reconnection it became 3, How I know that a reconnect happened, I know from network tab, and console which I added in connect.ts hook.

I tried binding socketInstance?.readyState to a useEffect as well, But the useEffect doesn't run when it get's reconnect even tho the readyState was changed.

Can you guys please help me why I'm getting 3 and handleDepost is failing because of that, It should be 1 because I already handled reconnection, and if socketInstance is changing why the useEffect is not running based on that, I added it in dependency array as well. Below is the code for that.

  useEffect(() => {
    console.log("socketInstance", socketInstance);
  }, [socketInstance]);

FYI guys last 3 code blocks are from same separate component. Thanks


Solution

  • It was resolved by shifting up my whole refs to states and using it as a wrapper around my app with useContext. It makes sure to update my app whenever there is a message or update from the socket instance (in the global state).