Building Real-Time Dashboards with WebSockets and React
Lessons from YPMC and logistics products where WebSocket-driven UIs improved user synchronization by 60% — connection lifecycle, state patterns, and failure handling.
When polling is not enough
Dashboards that show fleet status, appointment queues, or live portfolio metrics break trust when data feels stale. Polling every 5 seconds wastes bandwidth and still feels laggy.
At YPMC, I collaborated with backend teams to integrate WebSockets for real-time data flow, improving user synchronization by ~60% compared to interval polling on mobile and web surfaces.
Similar patterns applied at logistics products (Kawa) where dispatchers needed live rider and shipment state without manual refresh.
Architecture overview
Browser (React) ←→ WebSocket Gateway ←→ Event Bus ←→ Services
↑ ↓
TanStack Query Auth + rooms
(hydration + cache)
We did not replace REST — we complemented it:
- REST for initial hydration and mutations
- WebSockets for incremental domain events
Connection lifecycle in the client
A production hook must handle more than onmessage:
type SocketStatus = "connecting" | "open" | "reconnecting" | "closed";
export function useRealtimeChannel(roomId: string) {
const [status, setStatus] = useState<SocketStatus>("connecting");
const queryClient = useQueryClient();
useEffect(() => {
const socket = createSocket(`/rooms/${roomId}`);
socket.onopen = () => setStatus("open");
socket.onclose = () => setStatus("reconnecting");
socket.onmessage = (event) => {
const payload = parseEvent(JSON.parse(event.data));
queryClient.setQueryData(["room", roomId], (old) => mergeEvent(old, payload));
};
return () => socket.close();
}, [roomId, queryClient]);
return { status };
}
Expose connection status in the UI — users should know when data is live vs. disconnected.
State merging, not full replacement
Broadcasting entire dashboard snapshots does not scale. We used event-sourced patches:
{ "type": "SHIPMENT_STATUS_UPDATED", "id": "sh-991", "status": "in_transit" }
The client merges into cached entities. TanStack Query's setQueryData keeps React renders minimal.
Backpressure and throttling
High-frequency GPS updates can flood the main thread. We batched UI updates with requestAnimationFrame and throttled map marker re-renders to 4–6 fps for non-critical overlays.
Failure modes you must design for
- Reconnect storms — exponential backoff + jitter
- Duplicate events — idempotent handlers keyed by event ID
- Auth expiry — refresh token, resubscribe to rooms
- Mobile backgrounding — pause subscriptions, resync on foreground via REST
At YPMC, resolving mobile rendering bottlenecks reduced crash rates by ~20% — partly by reducing unnecessary re-renders when socket events arrived in bursts.
Testing realtime UI
- MSW WebSocket mock for component tests
- Playwright with controlled event injection in staging
- Load tests on gateway (backend-led, frontend observes frame times)
Key takeaways
- Use REST for hydration, sockets for deltas
- Surface connection state — silent failure erodes trust
- Merge events into normalized client state; avoid full snapshot pushes
- Throttle high-frequency updates; mobile devices are unforgiving