Repository navigation
Conversation
…bles for 'using' in tests Co-authored-by: alexander <alexander@n1s.se>
…eWaterfall' Co-authored-by: alexander <alexander@n1s.se>
Co-authored-by: alexander <alexander@n1s.se>
Co-authored-by: alexander <alexander@n1s.se>
…' with a plain 'experimental_waterfallHandle' whose reader starts the query Co-authored-by: alexander <alexander@n1s.se>
Adds an 'unmountOnDispose' helper to @tanstack/query-test-utils, so a render declared after its client unmounts before the client is cleared, and splits the helper tests into one file per helper. Co-authored-by: alexander <alexander@n1s.se>
… copy Co-authored-by: alexander <alexander@n1s.se>
Also keep optional elements in a handle's key type, and test streaming a pending query, infinite fetchHandle and server clients. The guide now covers promise transports and data that goes out twice. Co-authored-by: alexander <alexander@n1s.se>
Keep experimental_QueryHandle, experimental_QueryHandleWithData, experimental_QueryHandleFactory and experimental_FromHandleOptions. The guide and tests now cover a handle's data seeding the cache again after clear(), removeQueries or resetQueries, as any initialData does. Co-authored-by: alexander <alexander@n1s.se>
Every handle is now a plain { queryKey, input }. experimental_ensureHandle
and experimental_fetchHandle still wait for the data, but return the same
handle as experimental_prefetchHandle, and experimental_fromHandle adds no
initialData. Readers that need defined data use useSuspenseQuery.
Co-authored-by: alexander <alexander@n1s.se>
…Handle Covers a read-site select spread over the options, a select inside the factory, and a read-site select over a factory that already selects, for useQuery, useSuspenseQuery and the infinite hooks. Co-authored-by: alexander <alexander@n1s.se>
It gives the data a reader of a factory's query gets, the same type as useSuspenseQuery's data, so a component can type a prop that takes it. Co-authored-by: alexander <alexander@n1s.se>
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. CodeRabbit Verify
|
🦋 Changeset detectedLatest commit: b6bc296 The changes in this PR will be included in the next version bump. This PR includes changesets to release 24 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx affected --targets=test:sherif,test:knip,tes... |
❌ Failed | 7m 5s | View ↗ |
nx run-many --target=build --exclude=examples/*... |
✅ Succeeded | 40s | View ↗ |
☁️ Nx Cloud last updated this comment at 2026-11-03 21:50:17 UTC
|
Wanna test out the branch so approved the workflows to get a pkg.pr.new link |

Note
We made this PR with AI help and haven't yet done the two steps in TanStack's AI Contribution Policy. We still need to check that we understand all the code, and to rewrite this description in our own words.
This PR doesn't depend on #12511 and shares no code with it, so it targets
main. #12513 adds devtools support on top of this branch.To see the idea in action, try tRPC's live demo. It switches a blog's loaders between prefetching, ensuring and fetching, so you can see what each one waits for.
Motivation
A route loader (such as TanStack Router's
loader) or a Server Component already knows which data a page needs before the page renders, so it prefetches. Today the component that reads the data then has to repeat the query's key and options:queryFnandstaleTimehave to match in both places, or the loader and the component disagree on how to fetch.Query handles make the loader's call the single source of truth. The loader starts a query and returns a small serializable handle; the component gets the handle from
Route.useLoaderData()and doesn't repeat the key or the options. A query whose input you only know during render gets its handle fromexperimental_waterfallHandle, so the waterfall shows in the code.Usage
Integrations
tRPC shipped the same idea in its own package as
unstable_$prefetch,unstable_$ensure,unstable_$fetchandunstable_$fromHandle(trpc/trpc#7759). Its Prefetching with handles guide has the live demo linked above. With handles in query-core, an integration doesn't need its own version. A procedure's options function is already a factory, so an integration only adds a way to find the factory from a handle's key, and readers stop naming the procedure.trpcLikeProxy.test.tsxbuilds such a proxy on the public API alone: it starts queries on the server, sends the handles throughdehydrateand JSON, and reads them in the browser without naming the procedure again.🎯 Changes
query-core adds these as
experimental_APIs, and react-query re-exports them. Every existing API works as before.API
experimental_prefetchHandle(queryClient, factory, input)starts a query without waiting and never rejects; fetch errors show up where a component reads the handle.experimental_ensureHandlewaits for any data: it resolves at once with stale cached data and refetches it in the background.experimental_fetchHandlewaits for fresh data.experimental_fromHandle(factory, handle)returns the factory's options for the handle's input and adds nothing. Spread it to add read-site options likeselect.experimental_waterfallHandle(factory, input)builds a handle without starting anything; the reader fetches when it renders.experimental_waterfallHandle(factory, handle, switchInput)switches a loader's handle to a new input, and returns the loader's handle as is while the new input maps to the same key.experimental_QueryHandle,experimental_QueryHandleFactory,experimental_FromHandleOptionsandexperimental_InferQueryHandleData.Behavior
queryClient.query, orqueryClient.infiniteQueryfor options with aninitialPageParam, and followstaleTimethe same way on server and browser clients.{ queryKey, input }and carries no data. Readers get the data from the cache:useSuspenseQuerygives defined data, anduseQuerytypesdataas possiblyundefined, as usual.selectinfers both at the read site and inside the factory; a read-siteselectreplaces the factory's.experimental_InferQueryHandleDatagives the same type asuseSuspenseQuery'sdata, includingselectandInfiniteData.as constkeys keep handles from different factories apart. Only the types check this. The start functions take only the factory's input; passing a factory's extra parameters, like tRPC'squeryOptions(input, opts?), is a type error, because the handle couldn't carry them.Docs
A new guide,
framework/react/guides/query-handles, covers the API above, streaming pending queries throughshouldDehydrateQuery, serverstaleTime(withstaleTime: 0the browser refetches on mount), and nested dehydration that sends each query once per request.Tests
handles.test.tsx: the three start functions on browser and server clients, both waterfall forms, a customqueryKeyHashFn, infinite factories, plain handles with no data, and a reader with no data afterqueryClient.clear().queryHandles.test.tsxstreams a pending prefetched query throughdehydrateandHydrationBoundaryto auseSuspenseQueryreader that doesn't fetch it again.waterfallHandle.test.tsxcovers a reader that fetches once and a loader handle that stays until the input maps to another query.queryHandles.test-d.tsxandtrpcLikeProxy.test-d.tsx: inputs, keys that keep factories apart, data for every hook and start function,selectat the read site and in the factory, andexperimental_InferQueryHandleDataagainstuseSuspenseQuery'sdata.trpcLikeProxy.test.tsx: the tRPC-like proxy described above.using. This PR adds four small helpers for that to@tanstack/query-test-utils, each with a test:fakeTimers,clearOnDispose,subscribeandunmountOnDispose.✅ Checklist
pnpm run test:pr, or these tests do not apply to this pull request. (Ran oxlint, knip,test:typesacross all TS versions, and vitest for query-core, react-query and query-test-utils, plus the docs link check.)🚀 Release Impact