Documentation Index
Fetch the complete documentation index at: https://mintlify.com/get-convex/convex-react-query/llms.txt
Use this file to discover all available pages before exploring further.
Overview
ConvexQueryClient acts as a bridge between the Convex backend and TanStack Query’s QueryCache. It listens to cache events and manages WebSocket subscriptions so that your components always receive fresh data without polling.
The core flow is:
ConvexQueryClientsubscribes to the TanStackQueryCacheviaqueryCache.subscribe().- When a query with a
"convexQuery"key is added to the cache (the first observer mounts), it callsconvexClient.watchQuery()to open a subscription to the Convex backend over WebSocket. - When the Convex server pushes a new result,
ConvexQueryClientcallsqueryClient.setQueryData()to update the cache, which triggers React re-renders. - When the query is removed from the cache (after
gcTimeelapses with no observers),ConvexQueryClientcallsunsubscribe()on the underlyingWatchto close the Convex subscription.
Query key structure
Every Convex query is identified by a 3-element tuple query key:hashKey serializes query keys with JSON.stringify, which cannot correctly serialize Convex FunctionReference objects (they carry metadata like a function name string, not a plain value).
convexQueryClient.hashFn() handles this by serializing the key as:
queryKeyHashFn must be set globally when configuring the QueryClient:
Cache update flow
Component mounts
A component calls
useQuery(convexQuery(api.foo.bar, args)). TanStack Query
checks the cache for a matching key hash.Convex subscription created
ConvexQueryClient receives the "added" event and calls
convexClient.watchQuery(api.foo.bar, args), opening a WebSocket
subscription to the Convex backend.Initial fetch
ConvexQueryClient.queryFn() is called by TanStack Query to populate the
cache with the first value. On the client this runs a WebSocket query; on
the server it makes an HTTP request via ConvexHttpClient.Server pushes result
When the Convex server has a new result (on mount or after a data change),
the
onUpdate callback registered with watch.onUpdate() fires.Cache updated
ConvexQueryClient calls queryClient.setQueryData(queryKey, newValue) to
write the latest value into the TanStack Query cache.Subscription lifecycle
TanStack Query separates the concept of “observers” (mounteduseQuery hooks) from the cache entry itself. ConvexQueryClient uses this distinction to manage subscription lifetime:
- While at least one
useQueryobserver is mounted, the subscription is active and the cache entry is kept alive. - When the last observer unmounts (
"observerRemoved"event with zero remaining observers), TanStack Query starts thegcTimecountdown. The subscription remains active and the cached value is preserved during this window. - Once
gcTimeexpires, TanStack Query fires a"removed"event.ConvexQueryClientthen callsunsubscribe()on theWatchobject, closing the Convex WebSocket subscription.
Server vs. client behavior
ConvexQueryClient detects the environment with typeof window === "undefined" and adjusts accordingly.
| Environment | queryFn behavior |
|---|---|
| Server (SSR) | Uses ConvexHttpClient to make HTTP requests. No WebSocket is created. By default, uses consistentQuery so that multiple queries in the same render return results from the same logical timestamp. |
| Client | Uses the ConvexReactClient WebSocket connection for both the initial fetch and all ongoing subscriptions. |
You can opt out of consistent SSR queries by setting
dangerouslyUseInconsistentQueriesDuringSSR: true in
ConvexQueryClientOptions. This trades data consistency across queries for a
faster SSR render (one HTTP roundtrip instead of two).Error handling
When a Convex query function throws,ConvexQueryClient catches the error from watch.localQueryResult() and updates the query’s state directly:
error field returned by useQuery — with no additional configuration required.