Using SignalR with React

Using SignalR with React

Recently, I had the opportunity to work on a project where React and SignalR were integrated. This experience was quite insightful, and I am eager to share my learnings about this effective combination. SignalR is a library provided by Microsoft, designed to add real-time functionality to web applications. This means that applications can automatically push content to connected clients without them requesting it. Imagine a chat app where messages pop up instantly, or a stock monitoring tool where data refreshes in real time SignalR would be a good choice. Combining SignalR with React offers a dynamic platform for real-time web interactions. With the ability to push updates instantly, users get the data they need without any delays.

Setting Up the Server with SignalR

The heart of SignalR lies in its server-side component, which is typically set up using C#. To kick things off, you’ll need to install the appropriate Nuget package:

Install-Package Microsoft.AspNet.SignalR

Once installed, we need to use a ‘Hub’. Think of a hub as a communication centre where messages are broadcasted to all connected clients or targeted ones. Creating a hub is as simple as:

using Microsoft.AspNet.SignalR;

public class ChatHub : Hub
{
    public void Send(string message)
    {
        Clients.All.broadcastMessage(message);
    }
}

In this example, the Send method takes a message and broadcasts it to all connected clients. Speaking of broadcasting, let’s delve into that.

To send a message to all clients:

Clients.All.broadcastMessage("This message goes to everyone!");

But, what if we wanted to target a specific client?

The connectionId in SignalR is a unique identifier for each client connected to the SignalR hub. It’s automatically generated by the SignalR server and can be used to address individual clients. Here’s a bit more about how this works:

Understanding ConnectionId in SignalR

Whenever a client connects to a SignalR hub, it gets assigned a unique connectionId. This ID is useful in scenarios where you want to send a message to a specific client rather than broadcasting to all connected clients.

For instance, imagine a chat application where a private message needs to be sent to a particular user. You could use the connectionId to address and send that private message.

In the server-side C# code (in your Hub), you can access the connectionId of the currently connected client using Context.ConnectionId.

public class ChatHub : Hub
{
    public async Task SendMessageToSpecificClient(string user, string message)
    {
        string connectionId = GetConnectionIdForUser(user);
        await Clients.Client(connectionId).SendAsync("ReceiveMessage", user, message);
    }
    
    // This is just a mockup method; in real scenarios, you would have a way to associate users with connectionIds
    private string GetConnectionIdForUser(string user)
    {
        // Here, implement your logic to fetch the connectionId associated with the given user.
        // Maybe you stored this mapping in a dictionary or a database.
    }
}

In the above code, we’re sending a message to a specific client using the connectionId. However, in real-world scenarios, you would probably want to maintain a mapping between your application’s user IDs and SignalR connection IDs. This is because the connectionId is generated anew every time a client connects, and it’s more intuitive to address users by their usernames or user IDs rather than directly using the connectionId.

Remember, while the connectionId provides a way to uniquely identify a client during its session, it shouldn’t be used as a permanent identifier. Every time a client reconnects, a new connectionId will be assigned.

Reacting with SignalR

With the server ready, let’s bring React into the picture. Microsoft provides an official SignalR JavaScript client, which we can use to seamlessly integrate SignalR into our React components.

To set up, you’d typically install Microsoft’s SignalR library:

npm install @microsoft/signalr

Now, let’s illustrate a React component that actively listens for and displays SignalR messages:

import React, { useEffect, useState } from 'react';
import * as signalR from '@microsoft/signalr';

function SignalRComponent() {
    const [message, setMessage] = useState('');

    useEffect(() => {
        const connection = new signalR.HubConnectionBuilder()
            .withUrl("/chatHub")
            .build();

        connection.start()
            .then(() => console.log('Connection started'))
            .catch(err => console.log('Error while starting connection: ' + err));

        connection.on('broadcastMessage', (receivedMessage) => {
            setMessage(receivedMessage);
        });
    }, []);

    return (
        <div>
            <p>{message}</p>
        </div>
    );
}

In this version, the component starts by establishing a SignalR connection. When a ‘broadcastMessage’ is received, it updates the message state, which subsequently displays in a paragraph element.

Next, let’s craft a pair of components to depict how different components capture distinct messages:

// ComponentA.js
import React, { useEffect, useState } from 'react';
import * as signalR from '@microsoft/signalr';

function ComponentA() {
    const [message, setMessage] = useState('');

    useEffect(() => {
        const connection = new signalR.HubConnectionBuilder()
            .withUrl("/chatHub")
            .build();

        connection.start()
            .then(() => console.log('Connection started for Component A'))
            .catch(err => console.log('Error: ' + err));

        connection.on('messageForA', (receivedMessage) => {
            setMessage(receivedMessage);
        });
    }, []);

    return (
        <div>
            <p>{message}</p>
        </div>
    );
}

Both ComponentA and a hypothetical ComponentB function similarly, yet each subscribes to its respective message. When either of these components receives its designated message, it updates the local state, causing the message to render inside a paragraph element.

How to Obtain ConnectionId

When a client connects to a SignalR hub, the server can send back the connectionId for that specific client. To obtain the connectionId on the client-side (React in this case), you can set up a method on your hub that returns the connectionId and call that method immediately after establishing the connection. Here’s how you can do that:

First, extend your SignalR hub on the server side to include a method that returns the connectionId in the backend:

public class ChatHub : Hub
{
    public string GetConnectionId()
    {
        return Context.ConnectionId;
    }
    // ... rest of your hub methods ...
}

Now, in your React component, immediately after starting the connection, you can invoke the GetConnectionId method to retrieve the connectionId:

import React, { useState, useEffect } from 'react';
import { HubConnectionBuilder } from "@microsoft/signalr";

const SignalRComponent = () => {
    const [connection, setConnection] = useState(null);
    const [connectionId, setConnectionId] = useState(null);
    const [messages, setMessages] = useState([]);

    useEffect(() => {
        const newConnection = new HubConnectionBuilder()
            .withUrl("/chatHub") // Replace with your SignalR hub URL
            .build();

        setConnection(newConnection);
        
        newConnection.start()
            .then(() => {
                console.log("Connected!");

                // Get the connectionId after successfully connecting
                newConnection.invoke("GetConnectionId")
                    .then(id => {
                        console.log("ConnectionId is:", id);
                        setConnectionId(id);
                    })
                    .catch(err => console.error(err.toString()));

                // ... set up other SignalR event handlers here ...
            })
            .catch(err => console.log("Error establishing connection:", err));

        // Clean up the connection when the component is unmounted
        return () => {
            if (newConnection) {
                newConnection.stop();
            }
        };
    }, []);


    return (
        <div>
           <p>Your ConnectionId: {connectionId}</p>
        </div>
    );

Now, with this setup, you’ll see your connectionId rendered in the component after the connection is established. You can use this connectionId for whatever purpose you need, such as mapping users to their connectionIds or sending it to other services.

Suleyman Cabir Ataman, PhD

Architectural Design Patterns 1 – Layered (or Tiered) Pattern

Here in this first post of the blog, I want to also start with the first part of a series. Read more

Azure Blob Storage and C# Integration

Azure Blob Storage is one of Microsoft Azure's storage solutions. Think of it as a massive cupboard in the cloud Read more

MongoDB through C# – Using the Official Library

MongoDB is a popular NoSQL database, which means it doesn't use tables like traditional databases. Instead, it saves data as Read more

Redux vs. useContext: Navigating the State Management Maze

Every developer knows that the software engineering isn't just about writing code. It's about creating efficient, maintainable, and scalable systems. Read more

Sharing on social media:

Suleyman Cabir Ataman

Leave a Reply