Docs
BlogHomeStart building

React Realtime Hooks (totalum-sdk/react)


import … from 'totalum-sdk/react' — or the same namespace on totalum from 'totalum-sdk'. Install, configuration and errors: Totalum SDK.

The browser side of realtime rooms for React client components: useRealtimeChannel(room) connects to a room, keeps the messages received, shows who else is present, and reconnects by itself (every deploy drops sockets). The page never holds a key: it asks your own server route for a ticket (default POST /api/realtime/ticket), which calls totalum.realtime.ticket. RealtimeChannel is the same thing without React.

tsx
'use client';
import { useRealtimeChannel } from 'totalum-sdk/react';
const { messages, publish } = useRealtimeChannel('chat:team-1');
Method or typeWhat it does
useRealtimeChannelJoins a realtime room from a React client component and re-renders on every message: status, the messages received since it opened, others (with presence), and publish(data, event?).
RealtimeChannelOne room over one socket, without React (Vue, Svelte, plain scripts): connect(), read getSnapshot(), listen with subscribe(), send with publish(), close() when done.
RealtimeChannelOptionsHow useRealtimeChannel / RealtimeChannel get their ticket, announce presence and keep messages.
RealtimeMessageOne message received in a room.
RealtimePeerAnother connection present in the room (with the presence option).
RealtimeSnapshotThe current state of a room: what useRealtimeChannel returns (plus publish), and RealtimeChannel.getSnapshot().
RealtimeStatusThe state of a room's connection: connecting (first ticket and socket), open, reconnecting (after a drop — every deploy drops sockets — show it as an ordinary state, not an error) or closed (after close() or unmount).

#useRealtimeChannel

ts
useRealtimeChannel(room: string, options?: RealtimeChannelOptions): { publish, status, messages, others }

Joins a realtime room from a React client component and re-renders on every message: status, the messages received since it opened, others (with presence), and publish(data, event?). It asks your route for a ticket (POST /api/realtime/ticket with { room }, which calls totalum.realtime.ticket after your auth check), opens the socket, and re-tickets and reconnects with backoff after every drop. Render status === 'reconnecting' as an ordinary state, not an error. The options of the first render are kept; a new room opens a new connection.

Parameters

NameTypeRequiredDescription
roomstringyesThe room's name (the same your ticket route allows).
optionsRealtimeChannelOptionsnoWhere to get the ticket, presence data, and how many messages to keep.
options.ticketUrlstringnoThe app's route that mints the ticket; it receives { room } and answers { url } or { data: { url } }.
options.getTicket(room: string) => Promise<{ url: string; }>noOr mint the ticket yourself.
options.presenceMnoAnnounce this connection's presence with this metadata (name, colour…); others lists everyone else's.
options.keepnumbernoMessages kept in memory, newest last (default 100).
options.WebSocket{ new (url: string | URL, protocols?: string | string[] | undefined): WebSocket; prototype: WebSocket; readonly CONNECTING: 0; readonly OPEN: 1; readonly CLOSING: 2; readonly CLOSED: 3; }noA WebSocket implementation, for tests only. Default: the browser's.

Returns { publish, status, messages, others } — The room's snapshot (status, messages, others) and publish(data, event?), which sends to everyone in the room (you included) and returns false when the socket is not open.

Example

ts
'use client';
import { useRealtimeChannel } from 'totalum-sdk/react';

export function Chat({ teamId, name }: { teamId: string; name: string }) {
  const { messages, others, publish, status } = useRealtimeChannel(`chat:${teamId}`, { presence: { name } });
  return (
    <div>
      <p>{status === 'open' ? `${others.length} others online` : 'Connecting…'}</p>
      {messages.map((m) => <p key={m.id}><b>{m.identity.name}</b>: {String(m.data)}</p>)}
      <button onClick={() => publish('Hello!')}>Say hello</button>
    </div>
  );
}

Result:

ts
{
  status: 'open',
  messages: [{ id: '5f0c…', event: 'message', identity: { id: 'user_7', name: 'Bob' }, data: 'Hello!', ts: '2026-10-11T18:02:11.204Z' }],
  others: [{ connectionId: '1b9e…', identity: { id: 'user_7', name: 'Bob' }, meta: { name: 'Bob' } }],
  publish: [Function]
}

#RealtimeChannel

One room over one socket, without React (Vue, Svelte, plain scripts): connect(), read getSnapshot(), listen with subscribe(), send with publish(), close() when done. It re-tickets and reconnects by itself after a drop. useRealtimeChannel is a thin React wrapper over it.

MemberTypeDescription
roomstringThe room's name.
MethodDescription
subscribe(listener) → () => voidCalls listener on every change of the snapshot; returns the function that stops it.
getSnapshot() → RealtimeSnapshotThe current state: status, messages and others (a new object after each change).
connect() → voidFetches a ticket and opens the socket (and starts the presence heartbeat); does nothing if already connected.
publish(data, event?) → booleanSends data to everyone in the room (you included); false when the socket is not open.
close() → voidLeaves the room (announcing it to the others), closes the socket and stops reconnecting.

Example

ts
import { RealtimeChannel } from 'totalum-sdk/react';

const channel = new RealtimeChannel('chat:team-1', { presence: { name: 'Ana' } });
channel.subscribe(() => render(channel.getSnapshot()));
channel.connect();
channel.publish({ text: 'Hello' });
// later
channel.close();

#RealtimeChannelOptions

How useRealtimeChannel / RealtimeChannel get their ticket, announce presence and keep messages.

NameTypeRequiredDescription
ticketUrlstringnoThe app's route that mints the ticket; it receives { room } and answers { url } or { data: { url } }.
getTicket(room: string) => Promise<{ url: string; }>noOr mint the ticket yourself.
presenceMnoAnnounce this connection's presence with this metadata (name, colour…); others lists everyone else's.
keepnumbernoMessages kept in memory, newest last (default 100).
WebSocket{ new (url: string | URL, protocols?: string | string[] | undefined): WebSocket; prototype: WebSocket; readonly CONNECTING: 0; readonly OPEN: 1; readonly CLOSING: 2; readonly CLOSED: 3; }noA WebSocket implementation, for tests only. Default: the browser's.

#RealtimeMessage

One message received in a room.

FieldTypeAlways presentDescription
idstringyesThe message id.
eventstringyesThe event name the sender gave (message by default).
identity{ id, name? }yesWho sent it: the identity of their ticket, or { id: 'server' } for a server-side publish.
identity.idstringyesThe sender's user id.
identity.namestringnoThe sender's display name, when their ticket had one.
dataTyesWhat was sent.
tsstringyesWhen the room received it (ISO date-time).

#RealtimePeer

Another connection present in the room (with the presence option).

FieldTypeAlways presentDescription
connectionIdstringyesOne per open socket: the same user in two tabs is two peers with the same identity.
identity{ id, name? }yesThe peer's user, from their ticket.
identity.idstringyesTheir user id.
identity.namestringnoTheir display name, when their ticket had one.
metaMyesThe presence data they announced (name, colour, cursor…).

#RealtimeSnapshot

The current state of a room: what useRealtimeChannel returns (plus publish), and RealtimeChannel.getSnapshot().

FieldTypeAlways presentDescription
statusRealtimeStatusyesThe connection's state.
messagesRealtimeMessage[]yesThe messages received since the room opened, oldest first (at most keep).
othersRealtimePeer[]yesEveryone else present (with presence); empty otherwise.

#RealtimeStatus

The state of a room's connection: connecting (first ticket and socket), open, reconnecting (after a drop — every deploy drops sockets — show it as an ordinary state, not an error) or closed (after close() or unmount).

ValueMeaning
'open'Connected: messages arrive and publish works.
'connecting'Getting the first ticket and opening the socket.
'reconnecting'The socket dropped (every deploy drops sockets): getting a new ticket and reconnecting with backoff. An ordinary state, not an error.
'closed'Closed by close() or unmount.