Skip to content

How to handle SignalR reconnection logic in React without triggering useEffect on every state change? #64196

@0xbenon

Description

@0xbenon

Is there an existing issue for this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe the problem.

Hi, I have this case in my SPA:

I'm using SignalR in my project to receive some data from the backend. In the root of the project, example App.tsx, I create the connection to the Hub and store the connection in zustand store, to access the connection throughout the app.

Now, I have some component where I connect my users to "rooms" in the Hub connection. The user needs to select the Room (or rooms, they could join 1 or more), and I invoke the function in my hub to connect the user.

component A = (): JSX.Element => {
   const connection = useHubConnectionStore(state => state.connection);
   const [roomsSelected, setRoomsSelected] = useState<number[]>([]);

   const handleSelect = (check: boolean, roomId: number) => {
       try{
          const res = await connection?.invoke<ConectarUsuarioSucursalRoomResponse>("ConectarUsuarioSucursalRoom", roomId);
          
          var newRoomsValue = check ?
           [...roomsSelected, roomId]
           : roomsSelected.filter((id) => id !== roomId);
           
           setRoomsSelected(newRoomsValue);
       }catch(e){
         // show error
       }
   }

This works perfect. So ok, now the problem is that, I want to reconnect the users to the rooms from the client, in case the connection has been lost. The SignalR library has a handler for know when the connection was reconnected:

.onreconnected()

So I thought the correct way is, put that in useEffect:

component A = (): JSX.Element => {
   const connection = useHubConnectionStore(state => state.connection);
   const [roomsSelected, setRoomsSelected] = useState<number[]>([]);

   useEffect(() => {
      connection.onreconnected(async connectionId => {
        // iterate each roomSelected and .invoke again
      });
  
      return () => {//clean up handler, what is fun cause signalR has not implemented yet the offreconnected method}
   }, [connection, roomsSelected]);

   const handleSelect = (check: boolean, roomId: number) => {
       try{
          const res = await connection?.invoke<ConectarUsuarioSucursalRoomResponse>("ConectarUsuarioSucursalRoom", roomId);
          
          var newRoomsValue = check ?
           [...roomsSelected, roomId]
           : roomsSelected.filter((id) => id !== roomId);
           
           setRoomsSelected(newRoomsValue);
       }catch(e){
         // show error
       }
   }

But obviously, the problem is that SignalR doesn't have the method to clean the handler, and in every setRoomsSelected a new handler is registered. So the first thing I thought, was register the .onreconnected() method in the App.tsx (globally) where I create the connection, and for example, create new prop in the useHubConnectionStore called isReconnected or isConnected, but the things start to get complicated, and I'm not sure what's the best way to solve this without the method to clean the handler.

Describe the solution you'd like

A tip on how to solve it

Additional context

I already "solved" with the global onreconnected in App.tsx, storing the rooms for that component in the zustand store, but I don't like it cause now the .onreconnected() global handler, has specific logic for this component, imagine in future if I need do other stuff in other screens when the connection was reconnected.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-signalrIncludes: SignalR clients and servers

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions