Query Cache State Mechanism in OpenWork: TanStack Query Integration Explained
OpenWork implements a custom useQueryCacheState hook that subscribes directly to TanStack Query's cache events, exposing cached data as reactive React state while supporting fallback values for asynchronous route parameters.
OpenWork leverages TanStack Query for data fetching, but implements a custom query cache state mechanism to expose cached data as plain React state. This infrastructure layer allows UI components to subscribe directly to cache events via the useQueryCacheState hook, ensuring real-time synchronization with session data while supporting graceful fallbacks during route transitions.
The Foundation: Query Client Singleton and Configuration
Global Query Client Initialization
In apps/app/src/react-app/infra/query-client.ts, the getReactQueryClient function creates or retrieves a singleton QueryClient instance attached to globalThis.__owReactQueryClient. This ensures consistent cache behavior across the application lifecycle and prevents duplicate instantiation during React's strict mode double-mounting.
The client configuration sets default garbage collection time (gcTime) for internal queries, ensuring that session-related data persists long enough for UI components to consume it before eviction occurs.
Cache-to-State Bridge Implementation
The useQueryCacheState hook, defined in apps/app/src/react-app/infra/query-cache-state.ts, provides the core mechanism for cache state exposure. It accepts a typed queryKey and an optional fallback parameter, returning the current cache data as reactive state through an external store pattern.
How useQueryCacheState Subscribes to Cache Events
The hook leverages queryClient.getQueryCache().subscribe to listen for cache mutations. When a matching key updates, the hook invokes queryClient.getQueryData<T>(queryKey) and triggers a React re-render with the new value.
For null key handling, when the provided key is null (typically during route parameter resolution), the hook immediately returns the supplied fallback value. This prevents UI flickering between undefined states and loading states while asynchronous data resolves, keeping the component render output stable.
Implementation Examples in OpenWork
Session Transcript Component
Components consuming session data use the hook to maintain real-time synchronization with the transcript cache without triggering additional network requests:
import { useQueryCacheState } from "@/app/src/react-app/infra/query-cache-state";
function SessionTranscript() {
const transcript = useQueryCacheState(["react-session-transcript"], [] as string[]);
return (
<ul>
{transcript.map((msg, i) => (
<li key={i}>{msg}</li>
))}
</ul>
);
}
Conditional Data with Route Parameters
The fallback mechanism proves essential for components depending on dynamic route parameters that may be undefined during initial render:
function TodoList({ orgId }: { orgId?: string }) {
const todos = useQueryCacheState(
orgId ? ["react-session-todos", orgId] : null,
[] as string[]
);
return <pre>{JSON.stringify(todos, null, 2)}</pre>;
}
Performance Benefits of the External Store Pattern
By treating the TanStack Query cache as an external store, useQueryCacheState eliminates the automatic background refetching and network request management inherent to standard useQuery hooks. This approach suits OpenWork's architecture where session transcripts, status indicators, and todo lists require instant cache reactivity without network overhead.
The pattern ensures type safety through TypeScript generics (useQueryCacheState<T>) while maintaining compatibility with TanStack Query's garbage collection and cache invalidation strategies defined in the singleton client configuration.
Summary
- Singleton Pattern:
getReactQueryClientmaintains a globalQueryClientatglobalThis.__owReactQueryClientwith configuredgcTimedefaults for session data longevity. - External Store Bridge:
useQueryCacheStatesubscribes to cache events viaqueryClient.getQueryCache().subscribe, exposing data as React state throughqueryClient.getQueryData<T>. - Null Key Support: Passing
nullas the query key returns the fallback value immediately, preventing UI flicker during route parameter resolution. - Type Safety: Full TypeScript support with generic type parameters ensures compile-time safety for cached data structures consumed by UI components.
Frequently Asked Questions
How does useQueryCacheState differ from standard useQuery hooks?
Standard useQuery hooks manage the entire data fetching lifecycle including network requests, loading states, and background refetching. In contrast, useQueryCacheState only reads existing cache contents via queryClient.getQueryData and subscribes to cache updates, making it suitable for components that purely consume data already fetched elsewhere in the application.
Why does OpenWork attach the QueryClient to globalThis?
Attaching the client to globalThis.__owReactQueryClient ensures cache persistence across hot module reloads during development and prevents duplicate client instantiation during React's strict mode double-mounting. This singleton pattern maintains cache consistency throughout the application session according to the source code in infra/query-client.ts.
What happens when the query key is null in useQueryCacheState?
When the key parameter is null, the hook bypasses cache subscription and returns the provided fallback value immediately. This pattern supports components that depend on asynchronous route parameters, allowing them to render with default data while waiting for valid keys to populate, effectively eliminating "undefined" flicker states.
Where is the garbage collection time configured for internal queries?
The gcTime defaults are configured in apps/app/src/react-app/infra/query-client.ts within the getReactQueryClient function. These settings are specifically tuned for session-related queries to ensure data remains available for UI consumption before TanStack Query's garbage collection mechanism evicts the entries.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →