React js signalR using context and custom hook

171 Views Asked by At

I am a beginner react developer, I am using custom hook and context for the first time and still have some confusion about it. My goal with this approach was to write clean decoupled code that I can use to provide my components with signalR data without having to drilldown for them. I looked around on the internet and I found context and well after looking around I developed the code in order to provide data from signalR stream to react components. I have structured my code in this way:

  1. connection.js - class creates a singleton instance of signalR connection and provides function to communicate with signalR server. (Eg. ping, login, sendMessage etc)
 import * as signalR from "@microsoft/signalr";
    const URL = process.env.HUB_ADDRESS;
    class Connector {
      connection;
      events;
      ticks;
      static instance;
      constructor() {
        this.connection = new signalR.HubConnectionBuilder()
          .withUrl(URL)
          .withAutomaticReconnect()
          .build();

        this.connection.start()
          .then(() => {
            console.log("Connected to the hub ");
            this.connection.on("Connected", (data) => {
               //onMessageReceived("Connected");
            });
            this.connection.on("Pong", (data) => {
              console.log("Ping reply => ", {
                data
              })
              this.events(data);
            });
          }).catch(err => console.error("Error connecting to the hub:", err));

        this.events = (onMessageReceived) => {
          console.log("EVENT => ", onMessageReceived);
          this.connection.on("Connected", (data) => {
            onMessageReceived("Connected");
          });
          this.connection.on("Pong", (data) => {
            console.log("Ping reply II => ", {
              data
            })
            onMessageReceived(data);
          });
        };
        this.ticks = (onMessageReceived) => {
          this.connection.stream("SymbolQuotes")
            .subscribe({
              next: (quote) => {
                onMessageReceived({
                  quote
                })
              },
              complete: (data) => {
                if (data && data.error) {
                  console.error("Stream completed with error:", data.error);
                } else {
                  console.log("Stream completed successfully");
                }
              },
              error: (err) => {
                console.error("Error in stream:", err);
              }
            });
        };
      }

      disconnect = () => {
        this.connection.stop()
          .then(() => {
            console.log("Disconnected .... ");
          }).catch(err => console.error("Error disconnecting:", err));
      }

      connect = () => {
        console.log(this.connection.state);
        this.connection.send("ConnectCtrader", "zobad").then(data => {
            console.log("SENT")
          })
          .catch(err => console.error("Error sending connection request:", err));

      }

      newMessage = (messages) => {
        console.log(this.connection.state)
        if (messages === "Disconnect")
          this.connection.send("Disconnect", "zobad")
          .then(() => {
            console.log("SENT");
          })
          .catch(err => console.error("Error sending disconnect request:", err));
        else {
          this.connection.send(messages).then(() => {
            console.log("Message sent: Message>>> ", messages);
          }).catch((err) => {
            console.log("Message send failed: Message>>> ", messages);
            console.log("ERROR: ", err)
          });
        }
      }

      getStatus = () => {
        return this.connection.state;
      };

      cleanup = () => {
        this.connection.off("Connected");
        this.connection.off("Pong");
        this.connection.off("SymbolQuotes");
      };

      

    }
    export const getSignalRInstance = () => {
      if (!Connector.instance || Connector.instance.getStatus() === "Disconnected") {
        console.log("Creating a new connection instance")
        Connector.instance = new Connector();
      }
      return Connector.instance;
    }

  1. useSignalR.js - A custom hook and a context that provides the signalR instance to the components:
const SignalRContext = createContext();

export const useSignalR = () => {
    const INDEX_NOT_FOUND = -1;
    const [tickData, setTickData] = useState([]);
    const [ticker, setTicker] = useState({});
    const [connectionStatus, setConnectionStatus] = useState("Disconnected");
    const {connector} = useContext(SignalRContext);
    const {tick} = connector;
    const updatePrice = (price)=>{
        const { symbolId, symbolName, bid, ask, digits } = price;
        setTickData((prevTickData) => {
            const existingIndex = prevTickData.findIndex((item) => item.symbolId === symbolId);
            
            if (existingIndex !== INDEX_NOT_FOUND && prevTickData.length > 0) {
                const updatedPriceTick = [...prevTickData];
                updatedPriceTick[existingIndex] = { symbolId, symbolName, bid, ask, digits };
                
                return updatedPriceTick;
            } else {
                              
                return [...prevTickData, { symbolId, symbolName, bid, ask, digits }];
            }
        });
    };
    
    const connect = useCallback(() => {
        connector.connect();
        connector.ticks((tick) => {
            const quote = tick.quote;
            setTickData((prevTickData) => {
                const { symbolId, symbolName, bid, ask, digits } = quote;
                const existingIndex = prevTickData.findIndex((item) => item.symbolId === symbolId);
                
                if (existingIndex !== -1 && prevTickData.length > 0) {
                    const updatedPriceTick = [...prevTickData];
                    updatedPriceTick[existingIndex] = { symbolId, symbolName, bid, ask, digits };
                    return updatedPriceTick;
                } else {
                    return [...prevTickData, { symbolId, symbolName, bid, ask, digits }];
                }
            });
        });
    },[connector]);

    const disconnect = useCallback(() => {
        connector.disconnect();
        setConnectionStatus("Disconnected");
    },[connector]);
    const sendPing = useCallback(() => {
        console.info("Sending ping");
        connector.newMessage("Ping");
    },[connector]);

    const sendCustomMessage = useCallback((message) => {
        connector.newMessage(message);
    },[connector]);
    return {  connectionStatus, tickData, connect, disconnect, sendPing, sendCustomMessage  };
};

export const useSignalRContext = () => useContext(SignalRContext);
export const SignalRProvider = ({ children }) => {
    const connector = getSignalRInstance();
    return (
      <SignalRContext.Provider value={{ connector}}>
        {children}
      </SignalRContext.Provider>
    );
  };

Now I want to use this updated tickData(from useSignalR) in one of my component. How should I go about doing this? I have tried creating my component and tried to use tickData like so:

const MarketWatch = () =>{
    const hook = useSignalR();
    const {tickData, sendPing} = hook;

  console.log("MarketWatch ticks ", { tickData });
    
    return (
      <>             
      </>
    )
};
export default MarketWatch;

I am not getting the updated tickData in my component, however I have tested that in the custom hook the tickData seems to updating.

Can someone point me out if there is something I am doing wrong? Any mistake my beginner brain cannot figure out? Is there a better simpler approach of achieving my goal?

Thank you for any help in advance.

0

There are 0 best solutions below