socket.io-react-hooks-advanced
v1.0.8
Published
A modular and extensible React + Socket.IO hook library designed for real-world applications. Supports namespaced sockets, reconnection strategies, offline queues, latency monitoring, middleware, encryption, and more.
Maintainers
Readme
socket.io-react-hooks-advanced
A modular and extensible React + Socket.IO hook library designed for real-world applications. Supports namespaced sockets, reconnection strategies, offline queues, latency monitoring, middleware, encryption, and more.
✨ Features
- ✅ Auto-Reconnect with exponential backoff, customizable retry strategy
- ✅ Token Injection with support for refresh on unauthorized (401 responses)
- ✅ Latency Monitoring: track round-trip latency in real time (ping-pong events)
- ✅ Offline Background Queue: queue
emitcalls while offline, flush after reconnect - ✅ LocalStorage Queue Persistence: retain queue across page reloads, with TTL expiration
- ✅ Middleware for intercepting and transforming outgoing/incoming socket events
- ✅ AES Encryption of payloads (optional, using
crypto-js) - ✅ Namespaced Socket Providers with
createNamespacedSocket()for modular isolation - ✅ Fully Typed Hooks:
useSocketContext,useEvent,useLatency, etc. - ✅ Timeout-based ack() emits: emit events with callback, with timeout fallback behavior
- ✅ Event Subscription Helpers: auto cleanup with
useEvent()hook - ✅ Queue Overflow Handling: detect and manage maximum queue size violations
- ✅ Scoped Event Middleware: different middlewares per namespace
- ✅ Integrated Debug Logging: toggle log output for development
📦 Installation
npm install socket.io-react-hooks-advancedAlso install peer dependencies:
npm install react socket.io-client crypto-js🧠 Basic Usage
import React, {useEffect, useState} from "react";
import {
SocketProvider,
useSocketContext,
useEvent
} from "socket.io-react-hooks-advanced";
function App() {
return (
<SocketProvider
url="http://localhost:3000"
getToken={() => localStorage.getItem("token") || ""}
useEncryption={true}
encryptionKey="my-secret-key"
debug={true}
extraHeaders={{platform: "web", appId: "myAppId123"}} // x-platform, x-appId, other...
>
<Main/>
</SocketProvider>
);
}
function Main() {
const {connected, emit, queue, latency} = useSocketContext();
const [message, setMessage] = useState("");
const [response, setResponse] = useState<string | null>(null);
const [received, setReceived] = useState<string[]>([]);
useEvent("chat:receive", (msg) => {
setReceived((prev) => [...prev, JSON.stringify(msg)]);
});
const sendMessage = () => {
emit(
"chat:send",
{text: message},
{
ack: (res) => setResponse(JSON.stringify(res)),
timeout: 3000,
encrypt: true
}
);
setMessage("");
};
return (
<div>
<h2>Socket.IO React Example</h2>
<p>Status: {connected ? "🟢 Connected" : "🔴 Disconnected"}</p>
<p>Ping: {latency ?? "..."} ms</p>
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Type your message"
/>
<button onClick={sendMessage}>Send</button>
{response && <pre>Response: {response}</pre>}
{received.length > 0 && (
<div>
<h4>Received Messages</h4>
<ul>
{received.map((msg, i) => (
<li key={i}>{msg}</li>
))}
</ul>
</div>
)}
{queue.length > 0 && (
<div>
<h4>Offline Queue</h4>
<ul>
{queue.map((q, i) => (
<li key={i}>{q.event} - {JSON.stringify(q.data)}</li>
))}
</ul>
</div>
)}
</div>
);
}🧪 Namespaced Sockets
// chatSocket.ts
import {createNamespacedSocket} from "socket.io-react-hooks-advanced";
export const chatSocket = createNamespacedSocket();
// notifSocket.ts
import {createNamespacedSocket} from "socket.io-react-hooks-advanced";
export const notifSocket = createNamespacedSocket();
// App.tsx
import {chatSocket} from "./chatSocket";
import {notifSocket} from "./notifSocket";
function App() {
return (
<notifSocket.Provider url="http://localhost:3000" namespace="/notifications">
<chatSocket.Provider url="http://localhost:3000" namespace="/chat">
<Main/>
</chatSocket.Provider>
</notifSocket.Provider>
);
}
// Main.tsx
import React from "react";
import {Chat} from "./Chat";
import {Notifications} from "./Notifications";
export function Main() {
return (
<>
<Chat/>
<Notifications/>
</>
);
}
// Chat.tsx
import React, {useState, useEffect} from "react";
import {chatSocket} from "./chatSocket";
export function Chat() {
const {emit, connected} = chatSocket.useSocket();
const [msg, setMsg] = useState("");
const [log, setLog] = useState<string[]>([]);
chatSocket.useEvent("chat:message", (data) => {
setLog((prev) => [...prev, JSON.stringify(data)]);
});
return (
<div>
<h3>Chat Room</h3>
<p>Status: {connected ? "🟢" : "🔴"}</p>
<input
value={msg}
onChange={(e) => setMsg(e.target.value)}
placeholder="Type"
/>
<button onClick={() => emit("chat:message", {msg})}>Send</button>
<ul>
{log.map((l, i) => (
<li key={i}>{l}</li>
))}
</ul>
</div>
);
}
// Notifications.tsx
import React, {useEffect, useState} from "react";
import {notifSocket} from "./notifSocket";
export function Notifications() {
const {connected} = notifSocket.useSocket();
const [alerts, setAlerts] = useState<string[]>([]);
notifSocket.useEvent("notif:alert", (data) => {
setAlerts((prev) => [...prev, JSON.stringify(data)]);
});
return (
<div>
<h3>Notifications</h3>
<p>Status: {connected ? "🟢" : "🔴"}</p>
<ul>
{alerts.map((alert, i) => (
<li key={i}>{alert}</li>
))}
</ul>
</div>
);
}
You can create and use multiple isolated namespaces simultaneously.
📊 Latency Monitoring
import {useLatency} from "socket.io-react-hooks-advanced";
const PingIndicator = () => {
const latency = useLatency();
return <div>Ping: {latency ?? "..."} ms</div>;
};Latency is measured using ping/pong roundtrip at regular intervals.
🔐 Encrypted Emit
emit("secure-data", {value: "secret"}, {encrypt: true});Enable encryption in provider:
<SocketProvider
url="http://localhost:3000"
encryptionKey="my-secret-key"
useEncryption={true}
>
<App/>
</SocketProvider>Server must also decrypt the payload using the same AES key.
⚙️ Provider Options
| Prop | Type | Description |
|-------------------|-----------------------------------|--------------------------------------|
| url | string | Required socket server URL |
| namespace | string? | Socket.IO namespace |
| getToken | () => string \| Promise<string> | JWT token retriever |
| onUnauthorized | () => string \| Promise<string> | Token refresh when unauthorized |
| maxRetries | number | Max reconnection attempts |
| initialDelayMs | number | Backoff initial delay |
| backoffFactor | number | Backoff multiplier |
| persistQueue | boolean | Save queue in localStorage |
| queueKey | string | Storage key name |
| queueTTL | number | Expiry in ms for stored queue |
| maxQueueSize | number | Maximum offline queue size |
| onQueueOverflow | (event) => void | Called when queue exceeds max size |
| useEncryption | boolean | Enable payload encryption |
| encryptionKey | string | AES key for encryption |
| debug | boolean | Log connection/retry/status info |
| extraHeaders | record | add custom extra header when connect |
🧰 Hooks API
useSocketContext()
Returns:
socket: active socket instanceconnected: current connection statusemit(event, data, options): emit with ack/encryptionqueue: access queued eventslatency: current latency in ms
useEvent(event, handler)
Subscribes to a specific event and removes it automatically on unmount.
useLatency()
Returns current ping/pong latency (updated every 5s).
useConnectionStatus()
Returns the current socket lifecycle status, one of:
"connecting""connected""reconnecting""disconnected""unauthorized""failed"
Example usage:
import {useConnectionStatus} from "socket.io-react-hooks-advanced";
function StatusTag() {
const status = useConnectionStatus();
return <p>Status: <strong>{status}</strong></p>;
}🧱 Example: Multi-Namespace Setup
const notifSocket = createNamespacedSocket();
const gameSocket = createNamespacedSocket();
<notifSocket.Provider url="..." namespace="/notif">
<gameSocket.Provider url="..." namespace="/game">
<App/>
</gameSocket.Provider>
</notifSocket.Provider>Each namespaced socket provider is fully isolated.
📦 Offline Queue Handling
- Emit calls made when disconnected are pushed to a queue
- On reconnect, queued emits are flushed to server
- Optionally persisted in
localStoragewith TTL - Overflow handler called if queue exceeds
maxQueueSize
Queue is type-safe and timestamped for tracing/debugging.
🧩 Middleware Support
Register middleware to intercept emit or received events:
registerMiddleware({
onOutgoing: (event, data) => encrypt(data),
onIncoming: (event, data) => decrypt(data),
});Scoped middleware can also be registered per namespace.
🔐 AES Encryption
AES-256 encryption is implemented using crypto-js. Encryption must be enabled with a shared key:
<SocketProvider
url="..."
encryptionKey="shared-secret-key"
useEncryption={true}
>
<App/>
</SocketProvider>How it works:
emit(..., { encrypt: true })wraps payload- Middleware encrypts using AES
- Server must decrypt with same key (Node.js:
crypto)
🌐 TypeScript Support
All types are exposed and extensible:
SocketContextTypeQueuedEmitSocketProviderPropsNamespacedSocketSocketEmitOptions
Works out of the box with strict TypeScript settings.
🧪 Example: Emit with ACK and Timeout
emit("joinRoom", {roomId: 123}, {
ack: (response) => console.log("joined", response),
timeout: 3000,
});📜 License
MIT © Jingx
Feel free to contribute, submit issues, or request enhancements!
Built with ❤️ for scalable, socket-driven apps.
