-
Notifications
You must be signed in to change notification settings - Fork 119
Expose the full response from queryFn on utils.queryData #809
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
34b71d0
eeff5c1
197d435
3587737
fb31e81
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| --- | ||
| "@tanstack/query-db-collection": patch | ||
| --- | ||
|
|
||
| Add `queryData` property to `collection.utils` for accessing full query response including metadata. This resolves the common use case of needing pagination info (total counts, page numbers, etc.) alongside the data array when using the `select` option. | ||
|
|
||
| Previously, when using `select` to extract an array from a wrapped API response, metadata was only accessible via `queryClient.getQueryData()` which was not reactive and required exposing the queryClient. Users resorted to duplicating metadata into every item as a workaround. | ||
|
|
||
| **Example:** | ||
|
|
||
| ```ts | ||
| const contactsCollection = createCollection( | ||
| queryCollectionOptions({ | ||
| queryKey: ['contacts'], | ||
| queryFn: async () => { | ||
| const response = await api.getContacts() | ||
| // API returns: { data: Contact[], pagination: { total: number } } | ||
| return response.json() | ||
| }, | ||
| select: (response) => response.data, // Extract array for collection | ||
| queryClient, | ||
| getKey: (contact) => contact.id, | ||
| }) | ||
| ) | ||
|
|
||
| // Access the full response including metadata | ||
| const totalCount = contactsCollection.utils.queryData?.pagination?.total | ||
|
|
||
| // Perfect for TanStack Table pagination | ||
| function ContactsTable() { | ||
| const contacts = useLiveQuery(contactsCollection) | ||
| const totalRowCount = contactsCollection.utils.queryData?.total ?? 0 | ||
|
|
||
| const table = useReactTable({ | ||
| data: contacts, | ||
| columns, | ||
| rowCount: totalRowCount, | ||
| }) | ||
|
|
||
| return <TableComponent table={table} /> | ||
| } | ||
| ``` | ||
|
|
||
| **Benefits:** | ||
|
|
||
| - Type-safe metadata access (TypeScript infers type from `queryFn` return) | ||
| - Reactive updates when query refetches | ||
| - Works seamlessly with existing `select` function | ||
| - No need to duplicate metadata into items | ||
| - Cleaner API than accessing `queryClient` directly | ||
|
|
||
| The property is `undefined` before the first successful fetch and updates automatically on refetches. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -156,6 +156,7 @@ export interface QueryCollectionUtils< | |
| TKey extends string | number = string | number, | ||
| TInsertInput extends object = TItem, | ||
| TError = unknown, | ||
| TQueryData = any, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be |
||
| > extends UtilsRecord { | ||
| /** Manually trigger a refetch of the query */ | ||
| refetch: RefetchFn | ||
|
|
@@ -191,6 +192,25 @@ export interface QueryCollectionUtils< | |
| /** Get current fetch status */ | ||
| fetchStatus: `fetching` | `paused` | `idle` | ||
|
|
||
| /** | ||
| * The full query response data from queryFn, including any metadata. | ||
| * When using the select option, this contains the raw response before extraction. | ||
| * Useful for accessing pagination info, total counts, or other API metadata. | ||
| * | ||
| * @example | ||
| * // Without select - queryData is the array | ||
| * queryFn: async () => fetchContacts(), // returns Contact[] | ||
| * // queryData will be Contact[] | ||
| * | ||
| * @example | ||
| * // With select - queryData is the full response | ||
| * queryFn: async () => fetchContacts(), // returns { data: Contact[], total: number } | ||
| * select: (response) => response.data, | ||
| * // queryData will be { data: Contact[], total: number } | ||
| * const total = collection.utils.queryData?.total | ||
| */ | ||
| queryData: TQueryData | undefined | ||
|
|
||
| /** | ||
| * Clear the error state and trigger a refetch of the query | ||
| * @returns Promise that resolves when the refetch completes successfully | ||
|
|
@@ -206,6 +226,7 @@ interface QueryCollectionState { | |
| lastError: any | ||
| errorCount: number | ||
| lastErrorUpdatedAt: number | ||
| queryData: any | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be |
||
| observers: Map< | ||
| string, | ||
| QueryObserver<Array<any>, any, Array<any>, Array<any>, any> | ||
|
|
@@ -302,6 +323,10 @@ class QueryCollectionUtilsImpl { | |
| (observer) => observer.getCurrentResult().fetchStatus | ||
| ) | ||
| } | ||
|
|
||
| public get queryData() { | ||
| return this.state.queryData | ||
| } | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -574,6 +599,7 @@ export function queryCollectionOptions( | |
| lastError: undefined as any, | ||
| errorCount: 0, | ||
| lastErrorUpdatedAt: 0, | ||
| queryData: undefined as any, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we really need to type it as |
||
| observers: new Map< | ||
| string, | ||
| QueryObserver<Array<any>, any, Array<any>, Array<any>, any> | ||
|
|
@@ -731,6 +757,7 @@ export function queryCollectionOptions( | |
| state.errorCount = 0 | ||
|
|
||
| const rawData = result.data | ||
| state.queryData = rawData | ||
| const newItemsArray = select ? select(rawData) : rawData | ||
|
|
||
| if ( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With us having 1:n collection to query the
isXXreturn true when any of the queries are in that state,dataUpdatedAtis the most recent query update time, andfetchStatusis an array of statuses.