The client never retries a query or a mutation, so an error shows at once and a failed write never repeats, and doesn’t refetch when the window regains focus. Unused data stays in memory for maxAgeSeconds, 10 days by default, as long as it stays persisted.
Persistence is opt-in: only a query with meta: { persist: true } is saved, once it succeeds, so private or short-lived data never reaches the disk. Mutations are never saved, since one replayed after a reload could act twice. Change buster when a release changes a persisted query’s data, so older copies are thrown away.
Where IndexedDB is missing, such as in some private windows, the provider provides the client without persistence, and the persister does nothing.
clearQueryCache waits for the client’s restoration from its persisted copy, so restored data can’t come back after the clear. It then clears the memory and removes the persisted copy, and concurrent calls share one clear.
Paging after a first page
When one query loads a whole screen, including the first page of a list, useSeededInfiniteQuery pages through the rest of the list without fetching that first page again:
Each firstPageUpdatedAt and first page form a generation. When the screen’s query refetches, the list starts over from its new first page, any page in flight is aborted, and a late page of an older generation is dropped. Until the generation is published, the result is loading, with no data. Components using the same key share one generation, so one unmounting aborts nothing. fetchNextPage and refetch, which retries only the page that failed, keep their identity. The query never fetches by itself and is never persisted; gcTimeSeconds, 0 by default, keeps its pages after the last component unmounts. Something that forces it to fetch before the first page arrives rejects with FIRST_PAGE_NOT_READY.
Query state
The query-state helpers read the flags of any query, or of useSeededInfiniteQuery’s result: isQueryLoading, isQueryReadyForInitialDisplay, which holds back stale cached data until it’s refetched after mount, canQueryLoadMore, isQueryLoadingMore, and isQueryResultDataAvailable, for a list of queries.
Query boundaries
useQueryBoundary decides what a screen shows around the queries its content needs, and leaves the drawing to the app:
ts
import { useQueryBoundary } from 'jg-tanstack-query';const boundary = useQueryBoundary({ queries: [project, members], resolutionKey: projectId, // a new key hides the content until its queries resolve isAccessDenied: (error) => getErrorCode(error) === 'ACCESS_DENIED',});// { canRenderContent, isLoaderVisible, hasError, errorPresentation, failedQueries,// loadMoreQuery, retryFailed }
The content waits until every query has data for the key that’s fresh, or fetched since mount, so cached data from an earlier visit never shows as current. Once it shows, refetches, pagination, and background errors keep it on screen, with errorPresentation: 'retained'; an error before then, or one that leaves a query without data, is blocking. A new key, or an error isAccessDenied recognizes, hides the content in the same render, and after an access loss it returns only once every query settles with current data, even when isInitialResolutionApproved says the app approved the key’s data before. loadMoreQuery is the first query when it pages and the content shows, and retryFailed refetches every failed query; it and the result keep their identity while nothing changes. RECIPES.md has an unstyled boundary component.
Errors
Code
When
FIRST_PAGE_NOT_READY
A seeded infinite query was forced to fetch before its first page arrived
Generated from TSDoc
API reference
Every export of [email protected], 24 in all, with its signature, its errors, and an example.
Clears a client's cached data, in memory and persisted, such as at sign-out, so the next person on the device sees none of it. It first waits for the client's restoration from its persisted copy, so restored data can't come back after the clear. Concurrent calls share one clear.
Creates a persister that keeps a TanStack Query client's dehydrated copy in IndexedDB, through idb-keyval. It opens the database on first use, not when it's created, and does nothing where IndexedDB is missing.
Creates a TanStack Query client with safe defaults: queries and mutations never retry, a focused window doesn't refetch, and unused data stays in memory as long as it stays persisted. Create it once, outside any component.
Checks whether a query's data can be shown when a screen first renders: it didn't fail, and its data is fresh, or was fetched since the component mounted. Stale data from the cache waits for the refetch, so a screen never shows data from a previous visit as current.
Provides a TanStack Query client that restores its persisted copy on start, and persists the queries that opt in with meta: { persist: true }, never mutations. Where IndexedDB is missing, it provides the client without persistence.
Decides what a screen shows around the queries its content needs, without rendering anything, so any design system can draw the loader, the error, and the content.
The content waits until every query has data for the current resolutionKey that's fresh, or fetched since mount, so stale cached data never shows as current. Once it shows, refetches, pagination, and background errors keep it on screen, with errorPresentation: 'retained'. A new key, or an error isAccessDenied recognizes, hides it in the same render; after an access loss, it returns only once every query settles with current data, even with isInitialResolutionApproved.
The queries, the key of what they show, whether the app already approved its data, and how to recognize a lost access.
Returns
IQueryBoundaryState<TQuery>
Whether to render the content or a loader, the error state and the failed queries, the query for a load-more control, and a stable retryFailed. The result keeps its identity while none of it changes.
Pages through a list whose first page another query already fetched, such as one that loads a whole screen, without fetching that page again. The first page is published into an infinite query under queryKey, and continuationQueryFn fetches only the pages after it.
Each firstPageUpdatedAt and first page form a generation. A new generation cancels any page in flight and starts over from the new first page, and an older publication that finishes late never replaces a newer one. Components that use the same key share one generation and its pages, so one unmounting aborts nothing. Until the current generation is published, the result is loading, with no data. The query never fetches by itself and is never persisted.
What useSeededInfiniteQuery returns: a subset of TanStack's infinite query result, shaped so query boundaries and load-more controls read it like any other query. Until the current generation is published, it's loading, with no data. fetchNextPage and refetch keep their identity across renders.
what the content shows, such as a project's id: a new key hides the content until the queries resolve for it
isInitialResolutionApprovedproperty
boolean
whether the app already approved this key's data, such as before the boundary remounted, so cached data shows at once. It never brings content back after an access loss
isAccessDeniedproperty
(error: unknown) => boolean
whether a query's error means the user lost access, such as a 403, which hides the content at once, even approved, until every query succeeds again. Defaults to never
the key of the query that holds the pages, which no other query may use
firstPageproperty
TPage | undefined
the first page, which another query, such as one for a whole screen, already fetched
firstPageUpdatedAtproperty
number
when the first page was fetched, such as that query's dataUpdatedAt. A new value, or a new first page, starts a new generation: the pages loaded so far are dropped