Skip to content

fix: type infinite query options with a single page as TQueryFnData - #204

Merged
7nohe merged 3 commits into
mainfrom
claude/cool-lewin-042afa
Aug 11, 2026
Merged

fix: type infinite query options with a single page as TQueryFnData#204
7nohe merged 3 commits into
mainfrom
claude/cool-lewin-042afa

Conversation

@7nohe

@7nohe 7nohe commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Problem

Generated infinite hooks instantiated the options type with only two type arguments:

options?: Omit<UseInfiniteQueryOptions<TData, TError>, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam">
  & Partial<Pick<UseInfiniteQueryOptions<TData, TError>, "initialPageParam" | "getNextPageParam">>

TanStack Query v5's first slot is TQueryFnData — a single page — not TData. Since the hooks' TData defaults to InfiniteData<...>, the aggregate landed in the page slot and getNextPageParam handed callers InfiniteData<Page> as lastPage:

error TS2339: Property 'pets' does not exist on type 'InfiniteData<...>'.

Runtime behaviour was already correct (...options is spread after the defaults); this was purely a typing defect, but it made the documented override unusable without a cast.

Fix

The options are instantiated as <Page, TError, TData>, and the paginated queryFn resolves to that same page type so the two agree. Page is NonNullable<Common.XxxDefaultResponse>throwOnError: true means a resolved page is always present, so lastPage needs no optional chaining.

Only the first three type arguments are written. TanStack Query dropped TQueryData from UseInfiniteQueryOptions within the v5 line (6 params in 5.59.13, 5 in 5.101.4), so positions beyond TData are not stable across the ^5.0.0 peer range. Positions 1–3 are identical in 5.0.0, 5.59.13 and 5.101.4.

Two adjacent gaps close with it:

  • prefetchUse*Infinite omitted initialPageParam/getNextPageParam without re-adding them as optional, so overrides were type-forbidden even though ...options spreads last. getNextPageParam is spelled out rather than Picked, because FetchInfiniteQueryOptions only exposes it on the union member that also requires pages.
  • The infiniteQueryOptions factories took no options argument at all. They now accept the pagination overrides — deliberately just those, since the factory's return type is what every downstream consumer infers from and a wider options type (select, placeholderData, …) would make that inference ambiguous.

The default TData is unchanged (InfiniteData<Common.XxxDefaultResponse>), so no existing result type shifts; only TQueryFnData is NonNullable. The visible consequence is an asymmetry — lastPage.pets needs no chaining while data.pages[0]?.pets still does — kept deliberately to hold the diff to the defect.

Verification

  • examples/react-app/src/infiniteQueryTypes.ts is a new type-level regression test covering all four surfaces (hook, suspense hook, prefetch including pages, options factory). CI already runs tsc --noEmit over the example after codegen. Reverting the generator change makes it fail with the issue's exact TS2339.
  • It pins types with an Exact<T, U> helper rather than only checking that things compile, so the default TData, the explicit-TData escape hatch, and the suspense result type are all asserted unchanged. Since Exact<> is also satisfied by any, a @ts-expect-error on a non-existent property proves lastPage is genuinely the page type.
  • Cross-version: the generated output and the regression test compile against both @tanstack/react-query@5.59.13 (what the examples pin) and @tanstack/react-query@5.101.4 (latest), which is the claim the three-type-argument design rests on.
  • 199 unit tests pass (snapshots updated); branch coverage 91.2%, up from 90.07%.
  • react-app, nextjs-app and tanstack-router-app all regenerate and typecheck, and next build succeeds.

Not addressed: data.pageParams is still unknown. TPageParam is not reachable at a version-stable type-argument position, and the issue lists this as a corroborating symptom rather than expected behaviour.

closes #203, closes #139

The generated infinite hooks instantiated `UseInfiniteQueryOptions` with
only two type arguments, `<TData, TError>`. TanStack Query v5's first slot
is TQueryFnData — one page — not TData, and the hooks' TData defaults to
`InfiniteData<...>`, so the aggregate landed in the page slot and
`getNextPageParam` handed callers `InfiniteData<Page>` as `lastPage`.
Overriding the pagination scheme therefore needed a cast.

The options are now instantiated as `<Page, TError, TData>`, and the
paginated queryFn resolves to that same page type. Only the first three
type arguments are written: TanStack Query dropped TQueryData from
`UseInfiniteQueryOptions` within the v5 line, so later positions are not
stable across the `^5.0.0` peer range.

Two adjacent gaps close with it:

- `prefetchUse*Infinite` omitted `initialPageParam`/`getNextPageParam`
  without re-adding them as optional, so overrides were type-forbidden
  even though `...options` is spread after the defaults.
- The `infiniteQueryOptions` factories took no options argument at all
  and could not be customised. They now accept the pagination overrides.

Adds a type-level regression test to the react-app example, which CI
typechecks after codegen.

closes #203, closes #139
@vercel

vercel Bot commented Aug 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openapi-react-query-codegen Ready Ready Preview Aug 11, 2026 10:49am

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🟢 Lines 99.22% (🎯 95%) 514 / 518
🟢 Statements 98.51% (🎯 95%) 529 / 537
🟢 Functions 99.25% (🎯 95%) 133 / 134
🟢 Branches 91.73% (🎯 90%) 222 / 242
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/tsmorph/buildQueryHooks.mts 100% 100% 100% 100%
src/tsmorph/buildQueryOptions.mts 100% 100% 100% 100%
src/tsmorph/projectFactory.mts 100% 100% 100% 100%
Generated in workflow #442 for commit 9b681c6 by the Vitest Coverage Report Action

Exact<T, U> is satisfied by `any`, so the type assertions could have passed
vacuously. A @ts-expect-error on a non-existent property errors as unused if
the parameter is `any`, which makes the check definitive.

Also exercises `pages` in the prefetch overrides: FetchInfiniteQueryOptions
only accepts getNextPageParam on the union member that requires it, and that
options type changed shape here.
Every caller computed getPageType(op) only to hand it straight back, so the
parameter carried no information. Generated output is unchanged.

Also covers overriding initialPageParam in the regression test: TanStack Query
marks it required, so it has to be re-added as optional rather than merely
omitted, and only getNextPageParam was exercised.
@7nohe
7nohe merged commit edc64a3 into main Aug 11, 2026
5 checks passed
@7nohe
7nohe deleted the claude/cool-lewin-042afa branch August 11, 2026 10:58
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.

getNextPageParam override is mistyped: lastPage is InfiniteData<Page> instead of Page Issues with generating custom pagination infinite queries

1 participant