Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,13 @@ const Todos = () => {
// ✅ Define a Query Collection
import { createCollection } from '@tanstack/react-db'
import { queryCollectionOptions } from '@tanstack/query-db-collection'
import { QueryClient } from '@tanstack/query-core'

const queryClient = new QueryClient()

const todoCollection = createCollection(
queryCollectionOptions({
queryClient,
queryKey: ['todos'],
queryFn: async () =>
api.todos.getAll('/api/todos'),
Expand Down Expand Up @@ -289,18 +293,30 @@ You can do this:

```typescript
// Load normalized collections upfront (3 broader calls)
const todoCollection = createQueryCollection({
queryKey: ['todos'],
queryFn: fetchAllTodos,
})
const userCollection = createQueryCollection({
queryKey: ['users'],
queryFn: fetchAllUsers,
})
const projectCollection = createQueryCollection({
queryKey: ['projects'],
queryFn: fetchAllProjects,
})
const todoCollection = createCollection(
queryCollectionOptions({
queryClient,
queryKey: ['todos'],
queryFn: fetchAllTodos,
getKey: (todo) => todo.id,
}),
)
const userCollection = createCollection(
queryCollectionOptions({
queryClient,
queryKey: ['users'],
queryFn: fetchAllUsers,
getKey: (user) => user.id,
}),
)
const projectCollection = createCollection(
queryCollectionOptions({
queryClient,
queryKey: ['projects'],
queryFn: fetchAllProjects,
getKey: (project) => project.id,
}),
)

// Navigation is instant, no new API calls needed
const { data: activeProjectTodos } = useLiveQuery(
Expand Down
31 changes: 26 additions & 5 deletions src/blog/tanstack-db-0.5-query-driven-sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ You don't need a new API for every component. With 0.5, the component's query _i
const { data: projectTodos } = useLiveQuery((q) =>
q
.from({ todos })
.join({ projects }, (t, p) => eq(t.projectId, p.id))
.join({ projects }, ({ todos, projects }) =>
eq(todos.projectId, projects.id),
)
.where(({ todos }) => eq(todos.status, 'active'))
.where(({ projects }) => eq(projects.id, 123)),
)
Expand Down Expand Up @@ -106,9 +108,15 @@ Let's see how each works.
With 0.5, you add one line to your collection:

```tsx
import { QueryClient } from '@tanstack/query-core'

const queryClient = new QueryClient()

const productsCollection = createCollection(
queryCollectionOptions({
queryClient,
queryKey: ['products'],
getKey: (product) => product.id,
queryFn: async (ctx) => {
// Parse your query predicates into API parameters
const params = parseLoadSubsetOptions(ctx.meta?.loadSubsetOptions)
Expand Down Expand Up @@ -179,11 +187,19 @@ TanStack DB compares predicates across all live queries and deduplicates request
When you navigate from viewing 10 products to viewing 20, DB doesn't reload everything:

```tsx
import { createLiveQueryCollection } from '@tanstack/react-db'

// Initial query: loads 10 products
const { data } = useLiveQuery((q) => q.from({ products }).limit(10))
const productsQuery = createLiveQueryCollection((q) =>
q
.from({ products: productsCollection })
.orderBy(({ products }) => products.id)
.limit(10),
)
const { data } = useLiveQuery(productsQuery)

// User clicks "load more": loads ONLY the next 10
fetchNextPage()
await productsQuery.utils.setWindow({ limit: 20 })
```

```
Expand All @@ -206,7 +222,9 @@ Complex joins don't cause request explosions. They trigger a minimal set of filt
const { data } = useLiveQuery((q) =>
q
.from({ todos })
.join({ projects }, (t, p) => eq(t.projectId, p.id))
.join({ projects }, ({ todos, projects }) =>
eq(todos.projectId, projects.id),
)
.where(({ todos }) => eq(todos.status, 'active')),
)

Expand All @@ -225,8 +243,10 @@ Query Collection integrates with TanStack Query's `staleTime` and `gcTime`:

```tsx
const productsCollection = createCollection(queryCollectionOptions({
queryClient,
queryKey: ['products'],
queryFn: fetchProducts,
getKey: (product) => product.id,
staleTime: 5 * 60 * 1000, // 5 minutes
syncMode: 'on-demand'
}))
Expand Down Expand Up @@ -254,7 +274,8 @@ That's progressive mode: load what you need immediately, sync everything in the
```tsx
const todoCollection = createCollection(
electricCollectionOptions({
table: 'todos',
shapeOptions: { url: '/api/todos' },
getKey: (todo) => todo.id,
syncMode: 'progressive',
}),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ In practice, the React Native setup looks like this:
import { open } from '@op-engineering/op-sqlite'
import { createCollection } from '@tanstack/db'
import { queryCollectionOptions } from '@tanstack/query-db-collection'
import { QueryClient } from '@tanstack/query-core'
import {
createReactNativeSQLitePersistence,
persistedCollectionOptions,
Expand All @@ -109,11 +110,14 @@ const persistence = createReactNativeSQLitePersistence({
database,
})

const queryClient = new QueryClient()

export const shoppingItemsCollection = createCollection(
persistedCollectionOptions<ShoppingItem, string>({
persistence,
schemaVersion: 1,
...queryCollectionOptions({
queryClient,
queryKey: ['shopping-items'],
queryFn: async () => api.shoppingItems.getAll(),
getKey: (item) => item.id,
Expand Down
Loading