Types

All types are exported from both react-websocket and react-websocket/core.

import type { Status, Snapshot, WebSocketClient } from 'react-websocket';

Status

type Status = 'idle' | 'connecting' | 'open' | 'reconnecting' | 'closed';
StatusMeaning
idleNot connected and not trying to be. Nobody uses the client yet, or everyone stopped
connectingThe first attempt is in progress
openConnected. send() goes out immediately
reconnectingThe connection dropped or an attempt failed. A retry is scheduled or in progress
closedStopped by close() or after giving up. Stays closed until connect() or reconnect(), or until every hook unmounts. If nothing held it when it stopped, the next hook to mount starts it again

Snapshot

The client state that hooks render from. A new object is created on every change, and the same object is returned while nothing changes.

interface Snapshot<In> {
  readonly status: Status;
  /** Most recent message (after parse/json and schema), kept across reconnects. */
  readonly lastMessage: In | undefined;
  /**
   * Consecutive failed attempts since the last stable connection. A connection that closes
   * within `reconnect.minUptime` of opening counts as failed. 0 while open.
   */
  readonly attempt: number;
}

CloseInfo

interface CloseInfo {
  readonly code: number;
  readonly reason: string;
  readonly wasClean: boolean;
}

Passed to onClose, the 'close' event and shouldReconnect. Common codes: 1000 normal closure, 1001 going away, 1006 abnormal closure (no close frame, such as a network loss), 1008 policy violation, 1011 server error, 1012 service restart, 1013 try again later, 4000 heartbeat timeout (from this library), 4000–4999 application-defined. By default, 1002, 1003 and 1008 aren't retried (see retryableClose), and a 1013 is retried after a longer wait (see Overloaded servers). A 1012 or 1013 whose reason is a number of seconds is retried after that delay.

MessageError

class MessageError extends Error {
  name: string; // 'MessageError'
  /** The raw event.data that failed. */
  readonly data: unknown;
  /** Schema issues, when the message failed `schema`. */
  readonly issues: ReadonlyArray<{ readonly message: string }> | undefined;
  /** The error thrown by parse/JSON.parse or the schema, if any. */
  readonly cause?: unknown;
}

Reported through onError and the 'error' event when an incoming message fails parse/json or schema. The message is not delivered.

import { MessageError } from 'react-websocket';

useWebSocket(url, {
  json: true,
  schema: Message,
  onError(error) {
    if (error instanceof MessageError) {
      reportToSentry(error, { extra: { data: error.data, issues: error.issues } });
    }
  }
});

EventMap

The events a client emits and their listener signatures. Use them with client.on(type, listener) and useWebSocketEvent.

interface EventMap<In> {
  open: (event: Event) => void;
  message: (message: In, event: MessageEvent) => void;
  close: (close: CloseInfo) => void;
  error: (error: Event | MessageError | Error) => void;
}
EventWhen
openA socket opened, initially or after a reconnect. Listeners run before queued messages are sent, so a handshake sent from here goes first
messageA message arrived and passed parse and schema
closeA socket closed, whether or not it will be retried
errorA socket error Event, a MessageError, or the Error thrown by the WebSocket constructor or a url function

An exception thrown by a listener is re-thrown asynchronously, so it reaches your global error reporting without stopping other listeners.

UrlSource

type UrlSource = string | (() => string | Promise<string>);

A function is called before every connection attempt, including reconnects. If it throws or rejects, that counts as a failed attempt and is retried with backoff. See Authentication.

SendData

type SendData = string | ArrayBufferLike | Blob | ArrayBufferView;

Whatever WebSocket.prototype.send accepts in your TypeScript DOM lib.

SendOptions

Per-message options for send().

interface SendOptions {
  /** Drop the message instead of queueing it when the socket isn't open. */
  volatile?: boolean;
  /** Keep only the newest queued message with this key. */
  key?: string;
  /** Queue ahead of every message queued without `priority`. */
  priority?: boolean;
  /** While open, drop the message if more than this many bytes are already buffered. */
  maxBuffered?: number;
  /** Drop the message instead of sending it once it has been queued this many ms. */
  maxAge?: number;
  /** Called if the message is dropped from the queue without being sent. */
  onDrop?: () => void;
}
Option
volatileWhile not open, drop the message and return false instead of queueing it. For data that's stale by the time a reconnect finishes, such as player input
keyWhile not open, replace any queued message with the same key. The new one moves to the back of the queue (or of the priority messages, for a priority one). For state where only the latest value matters, such as an aim angle or a cursor position
priorityWhile not open, queue the message ahead of every message queued without priority, after earlier priority messages. Messages sent from open listeners still go first. For control messages that must not wait behind a backlog: cancel, interrupt, a token refresh
maxBufferedWhile open, drop the message and return false if more than this many bytes are already waiting in the browser's send buffer (socket.bufferedAmount). For high-rate streams on a slow link, where a late message is worse than none
maxAgeDrop the queued message instead of sending it once it has waited this many ms, for messages that go stale during an outage. Overrides queue.maxAge. Checked when the socket opens and whenever another message is queued
onDropCalled if the queued message is dropped without being sent: replaced by a newer message with the same key, evicted by queue.limit, or older than maxAge. Not called when the queue is cleared because the client closed or gave up, or when send() drops the message right away and returns false

volatile, key, priority and maxAge only matter while the socket isn't open, and maxBuffered only while it is. Once a message is handed to the browser, nothing can overtake it, so priority can't jump ahead of data already sent. Keep that buffer short by sending bulky streams with maxBuffered.

game.send({ type: 'input', dx, dy }, { volatile: true, maxBuffered: 4096 });
game.send({ type: 'aim', angle }, { key: 'aim' });
game.send({ type: 'leave' }, { priority: true });

See Real-time games.

MigrateOptions

interface MigrateOptions {
  url?: UrlSource;
  within?: number; // default 0
}

Options for client.migrate().

Field
urlWhere to connect instead: a URL, or a function called before the connection. Later reconnects go there too, until the client is closed, gives up or is torn down. Defaults to the client's current URL
withinMove at a random moment within this many ms, so clients asked together don't all reconnect at once. Defaults to 0

StandardSchemaV1

The Standard Schema interface. zod (3.24+), valibot (1.0+), arktype (2.0+) and other libraries implement it, so you can pass their schemas directly to the schema option. You don't need to import this type.

WebSocketClient

interface WebSocketClient<In = unknown, Out = unknown> {
  readonly url: UrlSource;
  readonly ready: Promise<void>;
  readonly socket: WebSocket | null;
  subscribe(listener: () => void): () => void;
  getSnapshot(): Snapshot<In>;
  on<K extends keyof EventMap<In>>(type: K, listener: EventMap<In>[K]): () => void;
  send(message: Out, options?: SendOptions): boolean;
  connect(): void;
  close(code?: number, reason?: string): void;
  reconnect(): void;
  migrate(options?: MigrateOptions): void;
  acquire(): () => void;
}
Member
urlThe URL or URL function the client was created with
readyResolves once the socket has opened. Reading it starts an idle client. It stays resolved through automatic reconnects, rejects when the client gives up, on close() and during a server render, and is a new pending promise after connect()/reconnect() restart a closed client. Safe to pass to React's use(). See Suspense
socketThe current WebSocket, or null. An escape hatch; prefer the client's methods
subscribe(listener)Called whenever the snapshot changes. Compatible with useSyncExternalStore
getSnapshot()The current Snapshot
on(type, listener)Listen to an event. Returns an unsubscribe function. Subscribing the same function twice creates two independent subscriptions
send(message, options?)Encode and send now, or queue until open (see queue and SendOptions). Returns true if sent immediately
connect()Start connecting, and stay connected even with no hooks mounted, until close(). Does nothing if already connecting or open
close(code = 1000, reason?)Close, stop reconnecting and clear the queue. Status becomes 'closed'. Like WebSocket.close(), it throws for a code or reason the socket rejects (browsers take 1000 or 3000–4999), and then nothing has changed
reconnect()Drop the current socket and connect again now, resetting the backoff. Also restarts a 'closed' client
migrate(options?)Move to a new connection without a gap: at a random moment within within ms, open one (to url, if given), then close the current one once it's open. See MigrateOptions and Deploys
acquire()Hold the connection open until the returned release function is called. The hooks use this; it's useful for non-React integrations. The release function is safe to call more than once