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()returnsfalsewhen the message never left the client — socket down, serialize threw, or the wire write threw.useSocketSendFailedobserves the same failures centrallyonDebugemits 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:
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:
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:
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:
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:
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.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
- Reconnection — backoff, state transitions, manual retry
- Authentication — handling token refresh on 401
- Undelivered sync — queue and replay offline sends