All posts

Building a Real-Time Dashboard with TanStack Query and WebSockets

·Alex Chen
ReactTanStack QueryWebSocketsTypeScript

TanStack Query (formerly React Query) is one of those libraries that, once you use it, you wonder how you ever managed without it. The combination of automatic background refetching, stale-while-revalidate caching, and dead-simple deduplication handles 90% of data fetching use cases elegantly.

But then there's the 10%: live data that needs to update in real time without polling.

The challenge

I was building an analytics dashboard that showed live request counts, error rates, and latency percentiles. The data needed to update every few seconds, and polling felt wasteful — we'd be hitting the database on every interval regardless of whether anything had changed.

WebSockets were the obvious answer, but I didn't want to abandon TanStack Query's caching layer. I wanted the best of both worlds: live updates and the ability to hydrate from a server-side initial fetch.

The approach

TanStack Query exposes a queryClient that lets you manually set and invalidate cached data. Combined with a WebSocket connection, you can push live updates directly into the cache:

const queryClient = useQueryClient()

useEffect(() => {
  const ws = new WebSocket('wss://api.example.com/metrics/live')

  ws.onmessage = (event) => {
    const metrics = JSON.parse(event.data)
    queryClient.setQueryData(['metrics', 'live'], metrics)
  }

  return () => ws.close()
}, [queryClient])

The consuming component doesn't need to know anything about WebSockets — it just uses useQuery as normal:

const { data: metrics } = useQuery({
  queryKey: ['metrics', 'live'],
  queryFn: fetchMetrics, // initial fetch
  staleTime: Infinity,    // don't auto-refetch; WS handles updates
})

The result

The dashboard now hydrates instantly on load from the server-side fetch, then transitions seamlessly to live WebSocket updates. If the WebSocket disconnects, TanStack Query's retry logic kicks in for the initial fetch, giving us a graceful fallback.

The pattern is simple enough that I've extracted it into a small hook we reuse across several dashboard pages. The key insight is that TanStack Query's cache is just a map — anything that can write to it can serve as a data source, not just HTTP requests.