Installation
pnpm add @hookraft/usewebsocketUsage
import { useWebSocket } from "@hookraft/usewebsocket"const { status, messages, lastMessage, send, disconnect, reconnect } = useWebSocket({
url: "wss://api.example.com/chat",
onConnect: () => console.log("Connected!"),
onDisconnect: () => console.log("Disconnected"),
onMessage: (data) => console.log("Received:", data),
onError: (err) => console.error("Error:", err),
reconnect: true,
reconnectAttempts: 5,
heartbeat: { message: "ping", interval: 30000 },
})The problem it solves
Building a WebSocket connection in React requires handling many things manually — connecting, reconnecting on failure, queuing messages while offline, keeping the connection alive with heartbeats, and cleaning up on unmount. Most developers either skip these features or spend hours getting them right.
// Without useWebSocket — 70+ lines of manual work
const wsRef = useRef(null)
const reconnectTimerRef = useRef(null)
const reconnectAttempts = useRef(0)
const messageQueue = useRef([])
const connect = () => {
wsRef.current = new WebSocket("wss://api.example.com")
wsRef.current.onopen = () => {
// flush queue, reset attempts...
}
wsRef.current.onclose = () => {
// reconnect with backoff...
}
wsRef.current.onerror = () => { /* ... */ }
wsRef.current.onmessage = (e) => { /* parse, store... */ }
}
// You write all of this. Every time.// With useWebSocket — everything handled
const { status, messages, send } = useWebSocket({
url: "wss://api.example.com",
reconnect: true,
heartbeat: { message: "ping", interval: 30000 },
})Full Examples
Chat room
A full real-time chat room. Messages arrive instantly, send works even if briefly disconnected (queued and flushed on reconnect).
import { useState } from "react"
import { useWebSocket } from "@hookraft/usewebsocket"
type ChatMessage = {
id: string
user: string
text: string
timestamp: number
}
function ChatRoom({ roomId }: { roomId: string }) {
const [input, setInput] = useState("")
const { status, messages, send, reconnectCount } = useWebSocket<ChatMessage>({
url: `wss://api.example.com/chat/${roomId}`,
reconnect: true,
reconnectAttempts: 10,
reconnectInterval: 2000,
heartbeat: {
message: "ping",
interval: 30000,
},
queueWhileOffline: true,
onConnect: () => console.log("Joined room"),
onDisconnect: () => console.log("Left room"),
onReconnect: (attempt) => console.log(`Reconnecting... attempt ${attempt}`),
onReconnectFailed: () => console.log("Could not reconnect"),
})
const sendMessage = () => {
if (!input.trim()) return
send({
id: crypto.randomUUID(),
user: "me",
text: input,
timestamp: Date.now(),
})
setInput("")
}
return (
<div>
{/* Connection status */}
<div>
<span>{status}</span>
{status === "reconnecting" && (
<span>Attempt {reconnectCount}</span>
)}
</div>
{/* Messages */}
<ul>
{messages.map((msg) => (
<li key={msg.id}>
<strong>{msg.user}:</strong> {msg.text}
</li>
))}
</ul>
{/* Input */}
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && sendMessage()}
placeholder="Type a message..."
disabled={status === "error"}
/>
<button onClick={sendMessage} disabled={status === "error"}>
Send
</button>
</div>
</div>
)
}Live dashboard
Real-time data feed — stock prices, sports scores, sensor readings. Reconnects automatically if the connection drops.
import { useWebSocket } from "@hookraft/usewebsocket"
type StockUpdate = {
symbol: string
price: number
change: number
}
function StockTicker() {
const { lastMessage, status, isConnected } = useWebSocket<StockUpdate>({
url: "wss://api.example.com/stocks",
reconnect: true,
messageLimit: 1, // we only care about the latest price
onMessage: (data) => console.log(`${data.symbol}: $${data.price}`),
})
return (
<div>
<span>{isConnected ? "Live" : "Offline"}</span>
{lastMessage && (
<div>
<span>{lastMessage.symbol}</span>
<span>${lastMessage.price.toFixed(2)}</span>
<span style={{ color: lastMessage.change >= 0 ? "green" : "red" }}>
{lastMessage.change >= 0 ? "+" : ""}{lastMessage.change.toFixed(2)}%
</span>
</div>
)}
</div>
)
}Manual connect
Set autoConnect: false to connect only when the user triggers it — useful for opt-in real-time features.
import { useWebSocket } from "@hookraft/usewebsocket"
function LiveNotifications() {
const { status, messages, connect, disconnect, isConnected } = useWebSocket({
url: "wss://api.example.com/notifications",
autoConnect: false, // don't connect on mount
reconnect: true,
})
return (
<div>
<button onClick={isConnected ? disconnect : connect}>
{isConnected ? "Turn off live updates" : "Turn on live updates"}
</button>
<p>Status: {status}</p>
<ul>
{messages.map((msg, i) => (
<li key={i}>{String(msg)}</li>
))}
</ul>
</div>
)
}Custom message parser
By default messages are parsed with JSON.parse. Use parseMessage to handle custom formats or filter out certain messages.
import { useWebSocket } from "@hookraft/usewebsocket"
type ServerEvent = {
type: "message" | "system" | "heartbeat"
payload: unknown
}
function EventStream() {
const { messages } = useWebSocket<ServerEvent>({
url: "wss://api.example.com/events",
// Custom parser — skip heartbeat messages
parseMessage: (raw) => {
try {
const parsed = JSON.parse(raw) as ServerEvent
if (parsed.type === "heartbeat") return null // skip
return parsed
} catch {
return null
}
},
})
return (
<ul>
{messages.map((event, i) => (
<li key={i}>
[{event.type}] {JSON.stringify(event.payload)}
</li>
))}
</ul>
)
}Send before connected (message queuing)
If queueWhileOffline is true (default), messages sent before the connection opens are queued and automatically flushed when connected.
import { useWebSocket } from "@hookraft/usewebsocket"
function App() {
const { send, queuedCount, status } = useWebSocket({
url: "wss://api.example.com",
queueWhileOffline: true, // default
})
return (
<div>
<p>Status: {status}</p>
{queuedCount > 0 && (
<p>{queuedCount} message(s) queued — will send when connected</p>
)}
{/* This works even before the connection opens */}
<button onClick={() => send({ text: "Hello!" })}>
Send (queued if offline)
</button>
</div>
)
}How reconnect works
When the connection drops, useWebSocket waits and tries again using exponential backoff — each retry waits longer than the last, up to a maximum delay.
Attempt 1 → wait 2s → reconnect
Attempt 2 → wait 4s → reconnect
Attempt 3 → wait 8s → reconnect
Attempt 4 → wait 16s → reconnect
Attempt 5 → wait 30s → reconnect (max interval)
↓
onReconnectFailed fires
status → "error"
You control this with reconnectAttempts, reconnectInterval, and maxReconnectInterval.
How heartbeat works
Some WebSocket servers close idle connections. The heartbeat keeps the connection alive by sending a ping message on a regular interval.
useWebSocket({
url: "wss://api.example.com",
heartbeat: {
message: "ping", // what to send
interval: 30000, // every 30 seconds
},
})Responses of "pong" or the same message as the heartbeat are automatically ignored and not added to the message list.
Options
| Prop | Type | Default |
|---|---|---|
url | string | - |
protocols | string | string[] | - |
autoConnect | boolean | true |
reconnect | boolean | true |
reconnectAttempts | number | 5 |
reconnectInterval | number | 2000 |
maxReconnectInterval | number | 30000 |
heartbeat | HeartbeatConfig | - |
messageLimit | number | 100 |
queueWhileOffline | boolean | true |
onConnect | () => void | - |
onDisconnect | (event: CloseEvent) => void | - |
onMessage | (data: T, event: MessageEvent) => void | - |
onError | (event: Event) => void | - |
onReconnect | (attempt: number) => void | - |
onReconnectFailed | () => void | - |
parseMessage | (raw: string) => T | null | - |
serializeMessage | (data: T) => string | - |
Returns
| Prop | Type | Default |
|---|---|---|
status | WebSocketStatus | - |
messages | T[] | - |
lastMessage | T | null | - |
isConnecting | boolean | - |
isConnected | boolean | - |
isDisconnected | boolean | - |
isError | boolean | - |
reconnectCount | number | - |
queuedCount | number | - |
send(data) | (data: T) => void | - |
sendRaw(data) | (data: string) => void | - |
connect() | () => void | - |
disconnect() | () => void | - |
reconnect() | () => void | - |
clearMessages() | () => void | - |
is(status) | (s: WebSocketStatus) => boolean | - |