Typed SignalR hooks for every framework
One core, seven adapters. Declare your hubs once and get typed events, typed calls, and a connection that recovers by itself.
import { useState } from "react";
import { createSignalRClient, method } from "@dammers/use-signalr-react";
const { SignalRProvider, useSignalRInvoke } = createSignalRClient({
hubs: {
"/hubs/rooms": {
methods: { GetHistory: method<[roomId: string], { text: string }[]>() },
},
},
});
function HistoryCount({ roomId }: { roomId: string }) {
const getHistory = useSignalRInvoke("/hubs/rooms", "GetHistory");
const [count, setCount] = useState(0);
const load = () => getHistory(roomId).then((m) => setCount(m.length)).catch(console.error);
return <button onClick={load}>{count} messages</button>;
}
export function App() {
return (
<SignalRProvider baseUrl="https://api.example.com" accessTokenFactory={() => "token"}>
<HistoryCount roomId="general" />
</SignalRProvider>
);
}import { createApp, ref } from "vue";
import { createSignalRClient, method } from "@dammers/use-signalr-vue";
import App from "./App.vue";
export const signalR = createSignalRClient({
hubs: {
"/hubs/rooms": {
methods: { GetHistory: method<[roomId: string], { text: string }[]>() },
},
},
});
createApp(App)
.use(signalR, { baseUrl: "https://api.example.com", accessTokenFactory: () => "token" })
.mount("#app");
// Call this composable in a component's setup().
export function useHistoryCount(roomId: string) {
const getHistory = signalR.useSignalRInvoke("/hubs/rooms", "GetHistory");
const count = ref(0);
const load = () => getHistory(roomId).then((m) => (count.value = m.length)).catch(console.error);
return { count, load };
}import { Component, signal } from "@angular/core";
import { bootstrapApplication } from "@angular/platform-browser";
import { createSignalRClient, method } from "@dammers/use-signalr-angular";
const { provideSignalR, injectHubInvoke } = createSignalRClient({
hubs: {
"/hubs/rooms": {
methods: { GetHistory: method<[roomId: string], { text: string }[]>() },
},
},
});
@Component({ selector: "app-root", template: `<button (click)="load()">{{ count() }} messages</button>` })
export class HistoryCount {
private getHistory = injectHubInvoke("/hubs/rooms", "GetHistory");
count = signal(0);
load() {
this.getHistory("general").then((m) => this.count.set(m.length)).catch(console.error);
}
}
bootstrapApplication(HistoryCount, {
providers: [provideSignalR({ baseUrl: "https://api.example.com", accessTokenFactory: () => "token" })],
});<script module lang="ts">
import { createSignalRClient, method } from "@dammers/use-signalr-svelte";
export const { provideSignalR, hubInvoke } = createSignalRClient({
hubs: {
"/hubs/rooms": {
methods: { GetHistory: method<[roomId: string], { text: string }[]>() },
},
},
});
</script>
<script lang="ts">
provideSignalR({ baseUrl: "https://api.example.com", accessTokenFactory: () => "token" });
const getHistory = hubInvoke("/hubs/rooms", "GetHistory");
let count = $state(0);
const load = () => getHistory("general").then((m) => (count = m.length)).catch(console.error);
</script>
<button onclick={load}>{count} messages</button>import { useState } from "preact/hooks";
import { createSignalRClient, method } from "@dammers/use-signalr-preact";
const { SignalRProvider, useSignalRInvoke } = createSignalRClient({
hubs: {
"/hubs/rooms": {
methods: { GetHistory: method<[roomId: string], { text: string }[]>() },
},
},
});
function HistoryCount({ roomId }: { roomId: string }) {
const getHistory = useSignalRInvoke("/hubs/rooms", "GetHistory");
const [count, setCount] = useState(0);
const load = () => getHistory(roomId).then((m) => setCount(m.length)).catch(console.error);
return <button onClick={load}>{count} messages</button>;
}
export function App() {
return (
<SignalRProvider baseUrl="https://api.example.com" accessTokenFactory={() => "token"}>
<HistoryCount roomId="general" />
</SignalRProvider>
);
}import { createSignal } from "solid-js";
import { createSignalRClient, method } from "@dammers/use-signalr-solid";
const { SignalRProvider, useSignalRInvoke } = createSignalRClient({
hubs: {
"/hubs/rooms": {
methods: { GetHistory: method<[roomId: string], { text: string }[]>() },
},
},
});
function HistoryCount(props: { roomId: string }) {
const getHistory = useSignalRInvoke("/hubs/rooms", "GetHistory");
const [count, setCount] = createSignal(0);
const load = () => getHistory(props.roomId).then((m) => setCount(m.length)).catch(console.error);
return <button onClick={load}>{count()} messages</button>;
}
export function App() {
return (
<SignalRProvider baseUrl="https://api.example.com" accessTokenFactory={() => "token"}>
<HistoryCount roomId="general" />
</SignalRProvider>
);
}import { LitElement, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { createSignalRClient, method } from "@dammers/use-signalr-lit";
const { createSession } = createSignalRClient({
hubs: {
"/hubs/rooms": {
methods: { GetHistory: method<[roomId: string], { text: string }[]>() },
},
},
});
const session = createSession({ baseUrl: "https://api.example.com", accessTokenFactory: () => "token" });
@customElement("history-count")
export class HistoryCount extends LitElement {
private rooms = session.hub(this, "/hubs/rooms");
private getHistory = this.rooms.invoke("GetHistory");
@state() private count = 0;
private load() {
this.getHistory("general").then((m) => (this.count = m.length)).catch(console.error);
}
render() {
return html`<button @click=${this.load}>${this.count} messages</button>`;
}
}Pick your framework
Typed from one contract
Declare hubs, events and methods once. Every event handler and invoke is typed from that contract.
Many hubs, one provider
One client serves all hubs. Each hub connects once, however many components use it.
Lazy hubs
A lazy hub connects when the first consumer mounts and stops after the last one leaves.
Recovers by itself
A lost connection comes back without app code. Reconnect callbacks let you refetch.
Teardown that lands
A leave call in a cleanup waits for the connection and is sent once.
SSR-safe, no runtime deps
Nothing connects on the server. Framework and SignalR packages are peers.