esc

Type to search...

Error handling

Real connections fail. The tab goes offline, the browser throttles background sockets, a stale token gets rejected, the server restarts. react-socket surfaces these as three concrete things you can react to:

  • send() returns false when the message never left the client — socket down, serialize threw, or the wire write threw. useSocketSendFailed observes the same failures centrally
  • onDebug emits typed events for deserialize failures, URL resolve failures, in-flight drops, and more
  • Standard React error boundaries still apply to any component that reads socket state

Send while offline

send() returns a boolean so you can show a failure state inline without reaching into the connection state:

SendWithFallback.tsx
const { send } = useSocketSend(manager);

function handleSend(text: string) {
  const ok = send({ type: "message", text });
  if (!ok) {
    toast.error("You're offline. Your message was not sent.");
  }
}

With many send sites, checking the boolean everywhere gets repetitive. useSocketSendFailed observes every failed send centrally — the message never left the client, and reason tells you why:

SendFailedBridge.tsx
useSocketSendFailed(manager, ({ data, ackId, reason }) => {
  // reason: "not-connected" | "serialize-error" | "transport-error"
  if (ackId) useChatStore.getState().markFailed(ackId);
});

Deserialize failures

If your deserialize function throws on a frame, the manager emits a deserialize-error debug event and skips it. Subscribe to see these instead of silent drops:

manager.ts
const manager = new WebSocketManager<TClientMsg, TServerMsg>({
  url: getWsUrl(),
  serialize: (msg) => JSON.stringify(msg),
  deserialize: (raw) => JSON.parse(raw),

  onDebug(event) {
    if (event.type === "deserialize-error") {
      reportError(event.error, { raw: event.raw });
    }
  },
});

The same callback is how you audit everything else: connection state changes, reconnect attempts, subscribe and unsubscribe, in-flight acks and drops. Good place to bridge to Sentry or your own logger.

URL resolution failures

If your url function throws or rejects (for example because the token endpoint is down), the manager emits a url-resolve-error debug event and schedules a reconnect with backoff:

manager.ts
onDebug(event) {
  if (event.type === "url-resolve-error") {
    // Token refresh failed. The manager will retry with backoff.
    // Surface a message so the user knows we are trying.
    status.setRefreshingAuth(true);
  }
}

Connection state as UX signal

The useSocketConnectionState hook gives you the only piece of state most UIs need. Map it to your own labels and disable actions that depend on a live socket:

ChatInput.tsx
function ChatInput() {
  const state = useSocketConnectionState(manager);
  const connected = state === "connected";

  return (
    <form>
      <input disabled={!connected} />
      <button disabled={!connected}>Send</button>
      {!connected && <p>We'll send your message as soon as you're back online.</p>}
    </form>
  );
}

Reconnect exhaustion

After reconnectMaxAttempts, the manager gives up and stays in "disconnected". Detect the transition and offer a manual retry:

manager.ts
manager.addConnectionStateListener(() => {
  const state = manager.getConnectionState();
  if (state === "disconnected" && manager.getSnapshot().reconnectAttempt >= MAX) {
    notify({
      title: "Connection lost",
      action: { label: "Retry", onClick: () => manager.connect() },
    });
  }
});

Error boundaries

react-socket hooks never throw on their own. Errors only come from your handlers inside useSocketEvent, onReady, and so on. Treat these like any other rendering code: wrap the subtree in an error boundary if you want to isolate failures.

Retry vs surface

  • Transient network drops: let the manager handle it. Show a subtle "reconnecting" indicator; do not block the UI.
  • Auth rejection: refresh the token and forceReconnect(). Only surface if the refresh itself fails.
  • Send while offline: show failure immediately, or queue and replay if the message is important.
  • Deserialize failure: report to your error tracker and keep going. One bad frame should not kill the session.
  • Reconnect exhausted: surface, with a manual retry. Silent endless retry is a worse UX than asking the user to refresh.

Next steps