Back to news

Building Real-Time Dashboards with React Query and WebSocket

How we combine TanStack React Query with WebSocket events to build dashboards that update in real-time without polling or manual refetching.

When we built the OMS module for BeproCRM, one of the key requirements was real-time order updates. Managers needed to see new orders, status changes, and overdue alerts without refreshing the page. This is the pattern we settled on.

The Problem with Polling

The naive approach is polling: fetch data every N seconds. But polling wastes bandwidth when nothing changes and feels laggy when something does. For a dashboard with 5 data panels, polling creates unnecessary server load and a bad user experience.

WebSocket + React Query Invalidation

Our approach: WebSocket events trigger React Query cache invalidation. The WebSocket connection listens for specific events (order.created, order.updated, payment.received). When an event arrives, we invalidate the relevant query keys, and React Query automatically refetches the stale data.

Best of both worlds. Data is always fresh because we refetch on actual changes. And we get React Query's built-in caching, deduplication, and loading states for free instead of rolling our own state management.

Practical Considerations

Reconnection handling. WebSocket connections drop. We use exponential backoff with jitter for reconnection, and on reconnect we invalidate all queries to ensure we haven't missed any events during the disconnection window.

Selective invalidation. Not every WebSocket event should refetch every query. We map event types to specific query keys so that an order update only refetches order-related queries, not the entire dashboard.

Optimistic updates. For actions initiated by the current user (like changing an order status), we update the cache optimistically before the server confirms. This makes the UI feel instant while the WebSocket event confirms the change for all other connected users.

The Result

The BeproCRM dashboard updates within 200ms of any change across all connected users. No polling, no manual refresh buttons, no stale data. That's the kind of real-time feel that makes internal tools actually pleasant to use.