Skip to content

feat(query-core, react-query): add experimental query handles, with experimental_getQueryHandleData - #6

Merged
cursor[bot] merged 3 commits into
cursor/experimental-query-handles-271efrom
cursor/query-handle-data-271e
Nov 6, 2026
Merged

cursor[bot] merged 3 commits into
cursor/experimental-query-handles-271efrom
cursor/query-handle-data-271e

Conversation

@KATT

@KATT KATT commented Nov 6, 2026 •

Copy link
Copy Markdown
Owner

Important

This adds one function on top of TanStack#12512, to show how a loader could read an awaited handle's data. Please discuss in TanStack#12512, not here.

🎯 Only difference from TanStack#12512

TanStack#12512 gives a loader a handle, but no way to read its data. To throw notFound(), a loader must call queryClient.getQueryData() with the factory's key. This PR adds experimental_getQueryHandleData(queryClient, factory, handle), which reads it from the handle.

Returned by TanStack#12512 This PR
experimental_queryHandle experimental_QueryHandle experimental_QueryHandle
experimental_queryHandleAsync experimental_QueryHandle experimental_DefinedQueryHandle
experimental_ensureQueryHandle experimental_QueryHandle experimental_DefinedQueryHandle

Before (TanStack#12512)

loader: async ({ context: { queryClient }, params }) => {
  const viewer = experimental_queryHandle(queryClient, viewerQuery)
  const post = await experimental_queryHandleAsync(queryClient, postQuery, {
    id: params.id,
  })
  if (!queryClient.getQueryData(postQuery({ id: params.id }).queryKey)) {
    throw notFound()
  }
  return { viewer, post }
},

After

loader: async ({ context: { queryClient }, params }) => {
  const viewer = experimental_queryHandle(queryClient, viewerQuery)
  const post = await experimental_queryHandleAsync(queryClient, postQuery, {
    id: params.id,
  })
  if (!experimental_getQueryHandleData(queryClient, postQuery, post)) {
    throw notFound()
  }
  return { viewer, post }
},
  • It takes only an experimental_DefinedQueryHandle. Passing a handle from experimental_queryHandle or experimental_waterfallHandle is a type error, because its query may not have data yet
  • The brand exists only in types. At runtime the handle is still a plain { queryKey, input }, and a defined handle works everywhere a plain one does
  • It returns TData like experimental_fromHandle: what the factory's select returns, or else the queryFn's data
  • It never fetches, waits or creates a query. Like getQueryData, it is a plain read from the cache
  • It respects the factory's queryKeyHashFn
  • The loader starts the other handles before it awaits, so reading the data doesn't create a waterfall. The guide says so in a new section

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested code changes locally with pnpm run test:pr, or these tests do not apply to this pull request. (Ran oxlint, knip, test:types across all TS versions, and vitest for query-core and react-query, plus the docs link check.)
  • I have followed the AI contribution policy and fully understand the code in this pull request, including any code generated with AI assistance.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).
Open in Web Open in Cursor 

…dles

experimental_queryHandleAsync and experimental_ensureQueryHandle now return a
type-branded experimental_DefinedQueryHandle. experimental_getQueryHandleData
takes only those and reads the query's data from the cache, without fetching,
waiting or creating a query, so a loader can throw notFound().

Co-authored-by: alexander <alexander@n1s.se>
@github-actions

github-actions Bot commented Nov 6, 2026

Copy link
Copy Markdown

🚀 Changeset Version Preview

2 package(s) bumped directly, 22 bumped as dependents.

🟨 Minor bumps

Package Version Reason
@tanstack/query-core 5.106.1 → 5.107.0 Changeset
@tanstack/react-query 5.106.1 → 5.107.0 Changeset
@tanstack/angular-query-experimental 5.106.1 → 5.107.0 Dependent
@tanstack/eslint-plugin-query 5.106.1 → 5.107.0 Dependent
@tanstack/preact-query 5.106.1 → 5.107.0 Dependent
@tanstack/preact-query-devtools 5.106.1 → 5.107.0 Dependent
@tanstack/preact-query-persist-client 5.106.1 → 5.107.0 Dependent
@tanstack/query-async-storage-persister 5.106.1 → 5.107.0 Dependent
@tanstack/query-broadcast-client-experimental 5.106.1 → 5.107.0 Dependent
@tanstack/query-devtools 5.106.1 → 5.107.0 Dependent
@tanstack/query-persist-client-core 5.106.1 → 5.107.0 Dependent
@tanstack/query-sync-storage-persister 5.106.1 → 5.107.0 Dependent
@tanstack/react-query-devtools 5.106.1 → 5.107.0 Dependent
@tanstack/react-query-next-experimental 5.106.1 → 5.107.0 Dependent
@tanstack/react-query-persist-client 5.106.1 → 5.107.0 Dependent
@tanstack/solid-query 5.106.1 → 5.107.0 Dependent
@tanstack/solid-query-devtools 5.106.1 → 5.107.0 Dependent
@tanstack/solid-query-persist-client 5.106.1 → 5.107.0 Dependent
@tanstack/vue-query 5.106.1 → 5.107.0 Dependent

🟩 Patch bumps

Package Version Reason
@tanstack/lit-query 0.2.36 → 0.2.37 Dependent
@tanstack/svelte-query 6.3.11 → 6.3.12 Dependent
@tanstack/svelte-query-devtools 6.3.11 → 6.3.12 Dependent
@tanstack/svelte-query-persist-client 6.3.11 → 6.3.12 Dependent
@tanstack/vue-query-devtools 6.3.11 → 6.3.12 Dependent

@cursor
cursor Bot changed the base branch from main to cursor/experimental-query-handles-271e November 6, 2026 10:16
cursoragent and others added 2 commits November 6, 2026 10:22
…cast

Give QueryFactoryOptions a select field so the factory's options keep their
type, and drop the throw for a missing cache entry, which can't happen right
after awaiting a handle.

Co-authored-by: alexander <alexander@n1s.se>
@cursor
cursor Bot merged commit 91bd116 into cursor/experimental-query-handles-271e Nov 6, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants