Skip to content

luciodale/react-socket

Repository files navigation

react-socket logo

react-socket

A TypeScript-first WebSocket manager for React. One hook per concern, zero imperative glue.

Documentation  ·  NPM  ·  GitHub

npm version npm downloads bundle size license

Why react-socket

Coming from react-use-websocket or a raw useEffect(() => new WebSocket(...)), these are the things you stop writing by hand:

  • Typed message schemas. Client and server union types flow through send, every hook, every callback. Discriminated unions narrow automatically by a configurable key.
  • One primitive per concern. Ten hooks, each with a distinct job. No message switch, no pub/sub layer, no .on / .remove anywhere in user code.
  • Ref counted subscriptions. Five components can subscribe to the same channel. One subscribe message hits the server. The unsubscribe fires on the last unmount.
  • Lifecycle in the library. Ack matching and subscription resolution are declared once as extractors. You never call ackInFlight or resolvePendingSubscription.
  • Offline message queue. Sends made while disconnected can persist to storage and flush on reconnect.
  • Reconnection with backoff. Exponential backoff with jitter, subscriptions restore themselves.
  • DevTools inspector. A drop in component that shows traffic, subscription ref counts, and in-flight state in real time.

Built for streaming LLM clients, realtime trading UIs, chat, presence, and agentic workflows.

Full comparisons: vs react-use-websocket (thin hook camp) · vs Socket.IO (same tier, different trade offs)

Requirements

  • React 16.8+ (hooks).
  • TypeScript 4.7+ recommended for full generic inference.
  • Modern evergreen browsers. Tested on Chrome 90+, Firefox 88+, Safari 14+, Edge 90+.

Install

npm install @luciodale/react-socket

Quick start

One manager at module level. One hook to react to incoming events. One hook to send.

import { useEffect, useState } from "react"
import {
  WebSocketManager,
  useSocketEvent,
  useSocketSend,
} from "@luciodale/react-socket"

type TClientMsg = { type: "echo"; text: string }
type TServerMsg = { type: "echo"; text: string }

const manager = new WebSocketManager<TClientMsg, TServerMsg>({
  url: "wss://your-server.com/ws",
  serialize: (msg) => JSON.stringify(msg),
  deserialize: (raw) => JSON.parse(raw),
})

export function Echo() {
  const [response, setResponse] = useState<string | null>(null)
  const { send } = useSocketSend(manager)

  useSocketEvent(manager, "echo", (msg) => setResponse(msg.text))

  useEffect(() => {
    manager.connect()
    return () => manager.disconnect()
  }, [])

  return (
    <>
      <button onClick={() => send({ type: "echo", text: "hello" })}>
        send
      </button>
      {response && <p>server said: {response}</p>}
    </>
  )
}

Change a field in TClientMsg or TServerMsg and TypeScript lists every call site that needs updating. useSocketEvent narrows the message via Extract<TServerMsg, { type: "echo" }> automatically.

The ten hooks

// React to an incoming message of a given type
useSocketEvent(manager, "notification", (msg) => { /* msg narrowed */ })

// Same as useSocketEvent, but buffers and flushes every flushMs (high-frequency streams)
useSocketEventBatch(manager, "tick", (msgs) => { /* ... */ }, { flushMs: 100 })

// Subscribe to a server-side stream, ref counted, auto cleanup
useSocketSubscription(manager, {
  key: roomId,
  subscribe: { type: "subscribe", channel: roomId },
  unsubscribe: { type: "unsubscribe", channel: roomId },
})

// True while a subscribe is in flight — drives "joining..." UI
const joining = useSocketPendingSubscription(manager, roomId)

// Typed positional send fn
const { send } = useSocketSend(manager)

// Fires on every send(), even offline — drives optimistic UI
useSocketSendIntent(manager, ({ data, ackId }) => { /* ... */ })

// Fires when in-flight messages are dropped on disconnect
useSocketInFlightDrop(manager, (messages) => { /* ... */ })

// Fires after every (re)connect, with the list of restored subscription keys
useSocketReady(manager, (restoredKeys) => { /* ... */ })

// Fires when the last subscriber for a key unmounts.
// 2nd arg is the original subscribe payload (first-payload wins).
useSocketLastUnsubscribe(manager, (key, subscribePayload) => { /* ... */ })

// Observable connection state
const state = useSocketConnectionState(manager)

Autocomplete useSocket in your editor — that is the entire surface.

Acknowledged sends

Tag a message with an ack id, wire the extractor once, the library clears in-flight tracking automatically when the server confirms.

const manager = new WebSocketManager<TClientMsg, TServerMsg>({
  url: "wss://...",
  serialize: JSON.stringify,
  deserialize: (raw) => JSON.parse(raw),

  // library auto-clears the matching in-flight entry when this returns an id
  getAckId: (msg) => (msg.type === "delivered" ? msg.ackId : undefined),
})
const { send } = useSocketSend(manager)

function onSend(text: string) {
  const id = crypto.randomUUID()
  send({ type: "message", id, text }, id) // 2nd arg: ackId
}

Subscriptions

Multiple components with the same key share a single server subscription. The manager dedupes automatically.

function ChatRoom({ roomId }: { roomId: string }) {
  useSocketSubscription(manager, {
    key: roomId,
    subscribe: { type: "subscribe", channel: roomId },
    unsubscribe: { type: "unsubscribe", channel: roomId },
  })

  const joining = useSocketPendingSubscription(manager, roomId)
  return joining ? <span>joining...</span> : <Room id={roomId} />
}

If three components mount ChatRoom with the same roomId, the subscribe message is sent once. When all three unmount, the unsubscribe fires once. Reconnect replays the subscription transparently.

Inspector

A built-in devtools panel for debugging WebSocket traffic. Separate export so it tree-shakes out of production builds.

import { InspectorPanel } from "@luciodale/react-socket/inspector"

function DevTools() {
  return <InspectorPanel manager={manager} />
}

Docs

Full documentation, patterns catalog, configuration reference, and live examples at koolcodez.com/projects/react-socket.

License

MIT

About

Type safe React hooks for real time WebSocket communication. Automatic reconnection, optimistic updates, and offline message queuing. Built for streaming LLM clients, real time dashboards, and collaborative apps.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors