npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-socket-link

v0.1.0

Published

Typed WebSocket library for React with auto-reconnect, pub/sub, and multi-connection support

Readme

react-socket-link

A lightweight, TypeScript-first WebSocket library for React featuring hook-based APIs, automatic reconnection with exponential backoff, typed pub/sub messaging, and flexible multi-connection management.

  • Named connections — useSubscription('chat', ...), useSend('notifications')
  • Static connections (known at startup) and dynamic ones (pattern-based, resolved at runtime)
  • Auto-reconnect with exponential backoff and full jitter
  • Message queue while disconnected, flushed on reconnect
  • Heartbeat / dead-connection detection
  • React Strict Mode safe

Installation

npm install react-socket-link

Quick start

1. Wrap your app with WebSocketProvider

import { WebSocketProvider } from 'react-socket-link';

function App() {
  return (
    <WebSocketProvider
      connections={{
        chat: { url: 'wss://api.example.com/chat' },
        notifications: { url: 'wss://api.example.com/notifications' },
      }}
    >
      <YourApp />
    </WebSocketProvider>
  );
}

2. Subscribe to messages in any component

import { useSubscription, useSend } from 'react-socket-link';

function ChatBox() {
  const send = useSend('chat');

  useSubscription('chat', '*', (message) => {
    console.log('received', message);
  });

  return <button onClick={() => send({ type: 'hello', payload: 'world' })}>Send</button>;
}

Typing your messages

Extend BaseMessage with a discriminated union to get exhaustive type checking in subscribers:

import type { BaseMessage } from 'react-socket-link';

type ChatMessage =
  | { type: 'chat';     payload: { user: string; text: string } }
  | { type: 'presence'; payload: { user: string; online: boolean } };

Then pass the type to hooks:

useSubscription<ChatMessage>('chat', 'chat', (msg) => {
  // msg is typed as { type: 'chat'; payload: { user: string; text: string } }
  console.log(msg.payload.text);
});

Using '*' as the event type receives every message as the full union:

useSubscription<ChatMessage>('chat', '*', (msg) => {
  // msg is ChatMessage — switch on msg.type for exhaustive handling
});

Provider

Static connections

Static connections are known at startup. Each key is the name you'll reference from hooks.

<WebSocketProvider
  connections={{
    general:       { url: 'wss://api.example.com/ws?type=general' },
    chat:          { url: 'wss://api.example.com/ws?type=chat' },
    notifications: { url: 'wss://api.example.com/ws?type=notifications', autoConnect: false },
  }}
>
  {children}
</WebSocketProvider>

Dynamic connections

Use the resolve prop for connections whose URLs depend on runtime values (room IDs, document IDs, etc.). It is called the first time a hook requests a name that isn't in the static map.

<WebSocketProvider
  connections={{ general: { url: '...' } }}
  resolve={(name) => {
    if (name.startsWith('room:')) {
      const id = name.slice(5);
      return { url: `wss://api.example.com/rooms/${id}` };
    }
    return null; // not handled — provider will throw a clear error
  }}
>
  {children}
</WebSocketProvider>

Return null or undefined for names your resolver doesn't handle. The provider throws a descriptive error rather than silently creating a broken connection.

Global error handler

Pass onError to capture errors from every connection (static and dynamic) in one place:

<WebSocketProvider
  connections={connections}
  resolve={resolveConnection}
  onError={(name, event) => {
    captureError(event, { tags: { connection: name } });
  }}
>
  {children}
</WebSocketProvider>

The name argument is the connection name ('chat', 'room:42', etc.).


Hooks

useSubscription

Subscribe to messages on a connection. Re-subscribes only when the connection name or message type changes — inline handler functions are safe to pass without causing churn.

useSubscription<ChatMessage>('chat', 'chat', (msg) => {
  console.log(msg.payload.text);
});

// Subscribe to all message types
useSubscription<ChatMessage>('chat', '*', (msg) => {
  switch (msg.type) { ... }
});

useSend

Returns a stable send function. Returns true if sent immediately, false if queued (when queueWhileDisconnected is enabled).

const send = useSend<ChatMessage>('chat');
send({ type: 'chat', payload: { user: 'alice', text: 'hello' } });

useConnectionState

Tracks the connection state as React state — triggers a re-render on each transition.

import { ConnectionState, useConnectionState } from 'react-socket-link';

const state = useConnectionState('chat');
// ConnectionState.CONNECTING | OPEN | CLOSING | CLOSED | RECONNECTING

useWebSocket

Returns the raw WebSocketClient instance. Use this when you need direct access — for example, to set up a per-component error handler:

const client = useWebSocket('chat');

useEffect(() => client.onError((event) => {
  console.error('chat error', event);
}), [client]);

useDynamicConnection

Manages a single dynamic connection that follows an ID. When the ID changes, the previous connection is closed and a new one is opened. On unmount, the active connection is closed automatically.

function Room({ roomId }: { roomId: string | null }) {
  // Returns 'room:42' when roomId is '42', null when roomId is null
  const connectionName = useDynamicConnection('room', roomId);

  useSubscription<RoomMessage>(
    connectionName ?? 'room:_placeholder',
    '*',
    (msg) => { ... },
  );
}

The provider's resolve function must handle the resulting name (room:42).

useCloseClient

Closes and removes a client by name. The next time a hook requests the same name, the client is recreated from config. Useful for explicitly tearing down dynamic connections.

const closeClient = useCloseClient();
closeClient('room:42');

useDynamicConnection handles this automatically on ID change and unmount. Use useCloseClient directly only when you need manual control.


Connection options

All options for WebSocketClientOptions:

| Option | Type | Default | Description | |---|---|---|---| | url | string | — | WebSocket URL (ws:// or wss://) | | protocols | string \| string[] | — | Sub-protocols for the native WebSocket | | autoConnect | boolean | true | Connect immediately on construction | | reconnect | boolean | true | Auto-reconnect on unexpected close | | maxReconnectAttempts | number | Infinity | Give up after this many attempts | | backoff | BackoffStrategy | exponential, capped at 30s | Delay between reconnect attempts | | queueWhileDisconnected | boolean | true | Queue outbound messages while offline | | maxQueueSize | number | 100 | Max queued messages (oldest dropped) | | heartbeat.message | TMessage \| string | { type: 'ping' } | Message to send as a ping | | heartbeat.intervalMs | number | 30000 | Ping interval in ms | | heartbeat.timeoutMs | number | 60000 | Silence threshold before force-reconnect | | serialize | (msg) => string \| ArrayBuffer \| Blob | JSON.stringify | Outbound message serializer | | deserialize | (data) => TMessage \| null | JSON.parse | Inbound message deserializer |


Backoff strategies

Two built-in strategies, or bring your own BackoffStrategy = (attempt: number) => number:

import { exponentialBackoff, fixedBackoff } from 'react-socket-link';

// Exponential with full jitter — prevents thundering herd on server restart
// Formula: random(0, min(cap, base * 2^attempt))
exponentialBackoff(1000, 30_000) // default

// Fixed delay — useful for tests or LAN apps
fixedBackoff(2000)

Framework-agnostic client

WebSocketClient has no React dependency and can be used in any environment:

import { WebSocketClient } from 'react-socket-link';

const client = new WebSocketClient<ChatMessage>({
  url: 'wss://api.example.com/chat',
  reconnect: true,
});

const unsub = client.subscribe('chat', (msg) => {
  console.log(msg.payload.text);
});

client.onStateChange((state) => console.log('state:', state));
client.onError((event) => console.error('error:', event));

client.send({ type: 'chat', payload: { user: 'alice', text: 'hello' } });
client.close();
unsub();

React Strict Mode

The provider is Strict Mode safe. In development, React mounts effects twice (mount → cleanup → remount) to surface bugs. The provider tracks which connections were active when the cleanup ran and reconnects only those on remount — connections configured with autoConnect: false are never prematurely started.