React cookbook · Advanced
Show live updates over WebSockets
Update a dashboard in real time when the server pushes events, with reconnects.
Approach
Keep server data in TanStack Query and use a WebSocket only for change notifications: on each message, update or invalidate the relevant query. Wrap the socket in a hook that reconnects with exponential backoff and cleans up on unmount.
Solution
import { useEffect } from "react";
import { useQueryClient } from "@tanstack/react-query";
type ServerEvent = { type: "order.updated"; order: Order } | { type: "orders.changed" };
export function useLiveOrders() {
const qc = useQueryClient();
useEffect(() => {
let socket: WebSocket | null = null;
let attempt = 0;
let timer: ReturnType<typeof setTimeout>;
let closed = false;
const connect = () => {
socket = new WebSocket(`${location.protocol === "https:" ? "wss" : "ws"}://${location.host}/ws/orders`);
socket.onopen = () => { attempt = 0; };
socket.onmessage = (msg) => {
const event = JSON.parse(msg.data) as ServerEvent;
if (event.type === "order.updated") {
qc.setQueryData<Order[]>(["orders"], (old = []) => old.map((o) => (o.id === event.order.id ? event.order : o)));
} else {
qc.invalidateQueries({ queryKey: ["orders"] });
}
};
socket.onclose = () => {
if (closed) return;
const delay = Math.min(30_000, 1000 * 2 ** attempt++) + Math.random() * 500;
timer = setTimeout(connect, delay);
qc.invalidateQueries({ queryKey: ["orders"] }); // resync after gaps
};
};
connect();
return () => { closed = true; clearTimeout(timer); socket?.close(); };
}, [qc]);
}
Pitfalls
- Reconnect storms without backoff/jitter.
- Missing events while disconnected — refetch on reconnect.
- Opening a socket per component instead of one shared connection.
Variations
- Server-Sent Events (
EventSource) when updates are one-way. - Managed realtime services (Ably, Pusher, Supabase Realtime).
Further reading: MDN: WebSocket
Reference: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
More React recipes
- Add optimistic updates to a todo list
- Build an accessible modal dialog
- Build infinite scroll with TanStack Query
- Call a cookie-authenticated API with CSRF protection
- Debounce a search input in React
- Handle errors gracefully with error boundaries
- Implement dark mode that follows the system
- Poll a long-running job's status