-
Notifications
You must be signed in to change notification settings - Fork 104
Open
Labels
Description
Background
TanStack Query provides several options for data initialization and transformation that are not currently exposed in query-db-collection
. While TanStack DB has its own patterns (like live queries with .select()
), some Query options would enhance the developer experience and provide feature parity.
Current State
- initialData: Only supported in
localOnlyCollectionOptions
, not inqueryCollectionOptions
- placeholderData: Not supported
- structuralSharing: Hard-coded to
true
in query.ts:320 - refetchOnWindowFocus/refetchOnReconnect: Not configurable
Proposed Features
1. Initial Data Support
Add initialData
option to QueryCollectionConfig
for pre-populating collections:
export interface QueryCollectionConfig<TItem, TError, TQueryKey> {
// ... existing options ...
/**
* Initial data to populate the collection before the first fetch
* Useful for SSR hydration or cached data from previous sessions
*/
initialData?: Array<TItem> | (() => Array<TItem>)
/**
* When the initial data was last updated (for staleness calculations)
*/
initialDataUpdatedAt?: number | (() => number)
}
Implementation approach:
- Apply initial data before starting the query observer
- Mark as synced data so it appears immediately in live queries
- Let TanStack Query's staleness logic determine if refetch is needed
2. Structural Sharing Configuration
Allow customizing how data updates are merged:
export interface QueryCollectionConfig<TItem, TError, TQueryKey> {
// ... existing options ...
/**
* How to structurally share data between updates
* - true: Use TanStack Query's default structural sharing
* - false: Always use new references
* - function: Custom sharing logic
*/
structuralSharing?: boolean | ((oldData: Array<TItem>, newData: Array<TItem>) => Array<TItem>)
}
3. Refetch Triggers
Expose window focus and reconnect options:
export interface QueryCollectionConfig<TItem, TError, TQueryKey> {
// ... existing options ...
/**
* Refetch on window focus
* @default true (when data is stale)
*/
refetchOnWindowFocus?: boolean | ((query: Query) => boolean)
/**
* Refetch on reconnect
* @default true (when data is stale)
*/
refetchOnReconnect?: boolean | ((query: Query) => boolean)
/**
* Network mode for offline behavior
* @default 'online'
*/
networkMode?: 'online' | 'always' | 'offlineFirst'
}
Implementation Examples
Example 1: SSR Hydration
// Server-side: fetch and serialize initial data
const initialTodos = await fetchTodos()
// Client-side: create collection with initial data
const todoCollection = createCollection(
queryCollectionOptions({
queryKey: ['todos'],
queryFn: fetchTodos,
getKey: (item) => item.id,
// Hydrate with server data
initialData: initialTodos,
initialDataUpdatedAt: Date.now() - 60000, // 1 minute old
// Keep using server data if fresh enough
staleTime: 5 * 60 * 1000, // 5 minutes
queryClient,
})
)
Example 2: Offline-First Configuration
const offlineCollection = createCollection(
queryCollectionOptions({
queryKey: ['offline-data'],
queryFn: fetchData,
getKey: (item) => item.id,
// Load from localStorage if available
initialData: () => {
const cached = localStorage.getItem('offline-data')
return cached ? JSON.parse(cached) : []
},
// Don't refetch on focus if we have local data
refetchOnWindowFocus: false,
refetchOnReconnect: true,
// Work offline-first
networkMode: 'offlineFirst',
queryClient,
})
)
Example 3: Custom Structural Sharing
const configCollection = createCollection(
queryCollectionOptions({
queryKey: ['config'],
queryFn: fetchConfig,
getKey: (item) => item.key,
// Custom structural sharing to preserve references for unchanged config values
structuralSharing: (oldData, newData) => {
const result = [...newData]
const newMap = new Map(newData.map(item => [item.key, item]))
// Reuse references for unchanged items
oldData.forEach((oldItem, index) => {
const newItem = newMap.get(oldItem.key)
if (newItem && deepEqual(oldItem, newItem)) {
result[index] = oldItem // Keep old reference
}
})
return result
},
queryClient,
})
)
Design Decisions
Why Not placeholderData
?
Placeholder data in TanStack Query shows temporary data while loading. In TanStack DB:
- Collections can have
initialData
that persists - Live queries show loading states via
isLoading
/isReady
- The local-first model means you often have real data to show
If needed, developers can implement placeholder UI at the component level.
Benefits
- SSR Support: Initial data enables server-side rendering hydration
- Offline Capability: Better control over offline behavior
- Performance: Custom structural sharing can optimize re-renders
- Feature Parity: Developers familiar with TanStack Query can use known patterns
Testing Requirements
- Test initial data is properly synced to collection
- Test staleness calculations with initialDataUpdatedAt
- Test refetch triggers respect configuration
- Test custom structural sharing functions
- Test network mode behavior
- Test that initial data doesn't interfere with query observer
Migration Guide
All changes are backwards compatible. Existing collections continue to work with defaults:
structuralSharing: true
refetchOnWindowFocus: true
(when stale)refetchOnReconnect: true
(when stale)- No initial data