# Query Cache State Mechanism in OpenWork: TanStack Query Integration Explained

> Discover OpenWork's query cache state mechanism. Learn how it uses TanStack Query integration to provide reactive React state and fallback values for async route parameters.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: internals
- Published: 2026-08-06

---

**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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/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:

```tsx
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:

```tsx
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**: `getReactQueryClient` maintains a global `QueryClient` at `globalThis.__owReactQueryClient` with configured `gcTime` defaults for session data longevity.
- **External Store Bridge**: `useQueryCacheState` subscribes to cache events via `queryClient.getQueryCache().subscribe`, exposing data as React state through `queryClient.getQueryData<T>`.
- **Null Key Support**: Passing `null` as 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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/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.