From 8e6112afb3cf5d28d435b7156b810046b3b25fa7 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Fri, 10 Jul 2026 11:58:36 -0600 Subject: [PATCH 1/4] docs(query-db-collection): document runtime QueryClient factory pattern --- docs/collections/query-collection.md | 50 ++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/docs/collections/query-collection.md b/docs/collections/query-collection.md index 6c90f53df..a24a58ec6 100644 --- a/docs/collections/query-collection.md +++ b/docs/collections/query-collection.md @@ -54,6 +54,56 @@ The `queryCollectionOptions` function accepts the following options: - `queryClient`: TanStack Query client instance - `getKey`: Function to extract the unique key from an item +### Creating Collection Options from a Runtime QueryClient + +`queryCollectionOptions` needs a `queryClient` when the collection options are created. In SSR, TanStack Start, tests, or multi-tenant apps, that `QueryClient` is often request-local or route-local rather than module-global. + +Keep shared collection configuration in a factory function that accepts the runtime `QueryClient`, then call that factory wherever the scoped client is available: + +```typescript +import { QueryClient } from "@tanstack/query-core" +import { createCollection } from "@tanstack/db" +import { queryCollectionOptions } from "@tanstack/query-db-collection" + +interface Todo { + id: string + title: string +} + +export function todoCollectionOptions(queryClient: QueryClient) { + return queryCollectionOptions({ + queryKey: ["todos"], + queryFn: async () => { + const response = await fetch("/api/todos") + return response.json() as Promise> + }, + queryClient, + getKey: (todo) => todo.id, + }) +} + +const queryClient = getRequestQueryClient() +const todosCollection = createCollection(todoCollectionOptions(queryClient)) +``` + +The same pattern works for scoped or parameterized collections. Pass route params, a tenant ID, or filters into the factory alongside the `QueryClient`: + +```typescript +export function projectTodosCollectionOptions( + queryClient: QueryClient, + projectId: string, +) { + return queryCollectionOptions({ + queryKey: ["projects", projectId, "todos"], + queryFn: () => fetchProjectTodos(projectId), + queryClient, + getKey: (todo) => todo.id, + }) +} +``` + +This keeps SSR and request-scoped code from sharing a global `QueryClient`. + ### Query Options Query Collections use TanStack Query internally, but `queryCollectionOptions` is not a full `QueryObserverOptions` pass-through. It exposes only the Query options supported by the collection adapter. Fields that affect row materialization, collection identity, and synchronization are handled by the adapter itself. From 23435c1f09acf18ad7dda5fe060e2fd9ed0bdc11 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Fri, 10 Jul 2026 11:59:01 -0600 Subject: [PATCH 2/4] Add changeset for runtime QueryClient docs --- .changeset/document-runtime-query-client-factory.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/document-runtime-query-client-factory.md diff --git a/.changeset/document-runtime-query-client-factory.md b/.changeset/document-runtime-query-client-factory.md new file mode 100644 index 000000000..1008b8b96 --- /dev/null +++ b/.changeset/document-runtime-query-client-factory.md @@ -0,0 +1,5 @@ +--- +"@tanstack/query-db-collection": patch +--- + +Document how to create query collection options from a request-local or route-local QueryClient. From 2f9240b420ebbb89f1c7d9b34094b36cdf2f8341 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 10 Jul 2026 18:00:44 +0000 Subject: [PATCH 3/4] ci: apply automated fixes --- .changeset/document-runtime-query-client-factory.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/document-runtime-query-client-factory.md b/.changeset/document-runtime-query-client-factory.md index 1008b8b96..6dcf42971 100644 --- a/.changeset/document-runtime-query-client-factory.md +++ b/.changeset/document-runtime-query-client-factory.md @@ -1,5 +1,5 @@ --- -"@tanstack/query-db-collection": patch +'@tanstack/query-db-collection': patch --- Document how to create query collection options from a request-local or route-local QueryClient. From feaf88e4663beb388bba77488e4d8713b7539b77 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Fri, 10 Jul 2026 13:54:50 -0600 Subject: [PATCH 4/4] docs(query-db-collection): clarify collection instance lifetime --- docs/collections/query-collection.md | 36 ++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/docs/collections/query-collection.md b/docs/collections/query-collection.md index a24a58ec6..ed9419e51 100644 --- a/docs/collections/query-collection.md +++ b/docs/collections/query-collection.md @@ -58,7 +58,7 @@ The `queryCollectionOptions` function accepts the following options: `queryCollectionOptions` needs a `queryClient` when the collection options are created. In SSR, TanStack Start, tests, or multi-tenant apps, that `QueryClient` is often request-local or route-local rather than module-global. -Keep shared collection configuration in a factory function that accepts the runtime `QueryClient`, then call that factory wherever the scoped client is available: +Keep shared collection configuration in a factory function that accepts the runtime `QueryClient`: ```typescript import { QueryClient } from "@tanstack/query-core" @@ -82,10 +82,36 @@ export function todoCollectionOptions(queryClient: QueryClient) { }) } -const queryClient = getRequestQueryClient() -const todosCollection = createCollection(todoCollectionOptions(queryClient)) +function createTodosCollection(queryClient: QueryClient) { + return createCollection(todoCollectionOptions(queryClient)) +} ``` +Create the collection once for each scoped `QueryClient` and parameter set, then reuse that `Collection` instance. Creating multiple collections with the same `QueryClient` and `queryKey` gives each collection its own materialized state, lifecycle, subscriptions, and optimistic mutations. + +In request-scoped environments, store the collection in request or router context. For client-side scopes, memoize by `QueryClient`: + +```typescript +type TodosCollection = ReturnType + +const collectionsByClient = new WeakMap() + +export function getTodosCollection( + queryClient: QueryClient, +): TodosCollection { + let collection = collectionsByClient.get(queryClient) + + if (!collection) { + collection = createTodosCollection(queryClient) + collectionsByClient.set(queryClient, collection) + } + + return collection +} +``` + +Avoid calling `createCollection(todoCollectionOptions(queryClient))` independently during render or in each consumer. Share the stable collection instance for the lifetime of that `QueryClient` scope. + The same pattern works for scoped or parameterized collections. Pass route params, a tenant ID, or filters into the factory alongside the `QueryClient`: ```typescript @@ -102,7 +128,9 @@ export function projectTodosCollectionOptions( } ``` -This keeps SSR and request-scoped code from sharing a global `QueryClient`. +For parameterized collections, memoize by both the scoped `QueryClient` and the parameter tuple. If a long-lived scope can create unbounded parameter sets, add eviction or dispose collections when they are no longer needed. + +This keeps SSR and request-scoped code from sharing a global `QueryClient` while keeping each collection instance stable within its scope. ### Query Options