When to Use Zustand vs TanStack Query for State Management in OpenWork
Use Zustand for synchronous, client-only UI state like sidebar toggles and persisted layout preferences, and use TanStack Query for asynchronous server state such as session transcripts and remote resources that require caching and background synchronization.
The OpenWork codebase maintains a strict architectural separation between local interface state and remote data fetching. Understanding when to apply Zustand versus TanStack Query for state management in OpenWork ensures optimal performance, instantaneous UI updates, and robust server data consistency. This guide maps the implementation patterns found in the source code to practical decision criteria.
The Architectural Split in OpenWork
The codebase categorizes state into two distinct types, assigning specific libraries based on the state’s origin and lifecycle requirements.
Zustand for Local UI State
Zustand manages synchronous state that lives entirely in the browser without network dependencies. In apps/app/src/react-app/shell/ui-state-store.ts, the store handles sidebar visibility, panel selections, and UI layout settings. Because this state requires no server round-trip, updates remain instantaneous and the footprint stays minimal.
TanStack Query for Server State
TanStack Query (via @tanstack/react-query) handles asynchronous data fetched from the backend. The apps/app/src/react-app/infra/query-client.ts file configures a shared QueryClient that implements caching, garbage collection through gcTime, and stale-while-revalidate semantics for resources like session transcripts and permission lists.
When to Choose Zustand
Select Zustand when your state meets these specific criteria:
- Purely client-side with no remote data dependencies.
- Instantaneous writes required without network latency or loading states.
- Local persistence needed across page reloads via
localStorageor similar mechanisms.
The implementation in ui-state-store.ts demonstrates this pattern using Zustand’s create function combined with persistence middleware. Domain-specific stores like apps/app/src/react-app/domains/session/notification-store.ts extend this pattern for localized feature state.
import { create } from "zustand";
type UiState = {
sidebarOpen: boolean;
setSidebarOpen: (open: boolean) => void;
toggleSidebar: () => void;
};
export const useUiStore = create<UiState>((set) => ({
sidebarOpen: true,
setSidebarOpen: (open) => set({ sidebarOpen: open }),
toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
}));
When to Choose TanStack Query
Adopt TanStack Query for state representing remote resources or asynchronous operations:
- Server data requiring intelligent caching, background refetching, and automatic retries.
- Query-key based identity for granular invalidation and subscription management.
- Garbage collection of unused queries to prevent memory leaks (configured via
gcTime).
The getReactQueryClient() function in query-client.ts establishes these behaviors with tuned gcTime settings for specific query families like ["react-session-transcript"].
import { useQuery } from "@tanstack/react-query";
import { getReactQueryClient } from "@/infra/query-client";
export function useSessionTranscript(sessionId: string) {
const client = getReactQueryClient();
return useQuery(
["react-session-transcript", sessionId],
async () => {
const resp = await fetch(`/api/sessions/${sessionId}/transcript`);
return resp.json();
},
{ client }
);
}
Combining Both Libraries in Components
OpenWork frequently composes both libraries within single components to maximize responsiveness while maintaining data consistency. UI components read local flags from Zustand while fetching remote data via TanStack Query hooks.
import { useUiStore } from "@/react-app/shell/ui-state-store";
import { useSessionTranscript } from "@/react-app/queries/session";
export function Sidebar() {
const { sidebarOpen, toggleSidebar } = useUiStore();
const { data, isLoading } = useSessionTranscript("current");
return (
<aside className={sidebarOpen ? "open" : "closed"}>
<button onClick={toggleSidebar}>Toggle</button>
{isLoading ? "Loading…" : <pre>{JSON.stringify(data, null, 2)}</pre>}
</aside>
);
}
In this pattern, the sidebar’s open/close flag lives in Zustand for immediate feedback, while the transcript data is fetched and cached by TanStack Query.
Key Implementation Files
| File | Role |
|---|---|
apps/app/src/react-app/shell/ui-state-store.ts |
Central Zustand store for UI layout and persisted preferences. |
apps/app/src/react-app/domains/session/notification-store.ts |
Domain-specific Zustand store demonstrating the persist middleware pattern. |
apps/app/src/react-app/infra/query-client.ts |
Creates the shared QueryClient and configures global GC policies. |
apps/app/tests/notification-store.test.ts |
Unit tests validating Zustand persistence behavior. |
Summary
- Zustand manages synchronous, client-only UI state like sidebar toggles and layout preferences, offering instantaneous updates and
localStoragepersistence viapersistUiState. - TanStack Query handles asynchronous server state including session transcripts and permission lists, providing caching, background refetching, and garbage collection via
gcTimeconfiguration. - The libraries are designed to work together: use Zustand for local responsiveness and TanStack Query for remote data consistency.
- Reference
ui-state-store.tsfor Zustand patterns andquery-client.tsfor TanStack Query configuration.
Frequently Asked Questions
Can I use Zustand for server state in OpenWork?
While technically possible, you should avoid using Zustand for server state. The OpenWork architecture reserves Zustand for client-only state because it lacks built-in caching, background refetching, and deduplication logic. TanStack Query provides these features through the QueryClient configured in query-client.ts, making it the correct choice for remote data.
How does OpenWork persist Zustand state across reloads?
OpenWork uses Zustand’s persist middleware to synchronize state with localStorage. The ui-state-store.ts and notification-store.ts files implement this pattern to preserve UI layout and notification preferences across browser sessions without requiring backend storage.
Why not use TanStack Query for UI state like sidebar toggles?
TanStack Query is optimized for asynchronous data fetching and caching, introducing unnecessary overhead for synchronous UI toggles. Using it for local UI state would create artificial network latency patterns and complicate the garbage collection lifecycle. Zustand provides the immediate, synchronous updates required for interface interactions.
How do I configure the QueryClient in OpenWork?
Import getReactQueryClient() from apps/app/src/react-app/infra/query-client.ts to access the shared instance. This function pre-configures default gcTime values and caching strategies used throughout the application, ensuring consistent behavior for queries like ["react-session-transcript"].
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 →