Skip to content

useQuery return type NoInfer preserves reverse-inference guard but breaks discriminated-union narrowing #11018

Description

@hsheth2

Context

useQuery currently returns UseQueryResult<NoInfer<TData>, TError>. That NoInfer guard is important: it prevents reverse inference from an annotated result type.

Without the guard, TypeScript can accept an incorrect annotation like this by letting the annotation influence TData:

const result: UseQueryResult<{ wow: string }> = useQuery({
  queryKey: ['key'],
  queryFn: () => ({ wow: true }),
  initialData: () => undefined as { wow: boolean } | undefined,
})

That should be a type error because the query options produce { wow: boolean }, not { wow: string }. That is the behavior fixed in #8683.

Describe the bug

The regression is that after #10593 switched this return type to TypeScript's built-in NoInfer, discriminated-union narrowing on data no longer works in normal code:

type Result =
  | { type: 'first'; first: string }
  | { type: 'second'; second: string }

const query = useQuery({
  queryKey: ['example'],
  queryFn: (): Result => ({ type: 'first', first: 'a' }),
})

const second = query.data?.type === 'first' ? undefined : query.data
second?.second

This is not asking to remove NoInfer. Removing it would regress the reverse-inference guard. The issue is that built-in NoInfer<TData> on the return type appears to preserve that guard while also blocking useful union narrowing.

Your minimal, reproducible example

https://www.typescriptlang.org/play/?ts=5.9.3#code/C4TwDgpgBAgg7gQwJbAgEwGoIDYFcIA8AKgHxQC8URUEAHqgHZoDOUACgE4D2Atks4SQMAZhA5QAqmQD8kqAC4qAKFCRJAgIr4OIAEoRmubMGIARBMARlKAbyhoLCRUXOWoAHyi4mEYUPRQAL4q4NAA8mDASFwMzMRaYiAAYgyuCAA0VGkUsIgo6Fh4hEQJOilpJNZQNkpQUACO2skMigAUAJQUZCVN5Y61UEIoSDhp0m2d5GTwyKiYOPjxvamOZJ7eaL7+aErBqtAAcggc3IgARtgQB1wAkiJixFXUdIwsXgwA1gxccAxQstc7qIOI8FFAGBAAG5iJRKTYAY2wx2gwm88KiMS8miaABkuFwBEtEn03JRvF8fgxMi5HDkZvl5kUiWUVpZKq0BlxItFYooIhjYszmmlqRV0kp2ooJNjEvpDMYzKs4RBEcioKiGOieViIKVkhBNeghSSEDlyd9fqLaZR6XNCosesTWVYSBy6lyBcw+dyYnFHSyRVlVuLJepdU05UYTID7iCaWySMrVRwUWiBTq9aZ+MAOEgzrg5sbnWbPhaqUHSblZgUFsU9Sb2ZyfbyoPyeX7686rWyQ1KZTpIwqjicfggLldbrHFQnYfDfcAoNh8QIAOrcBgAcz74dlBijBDscB+imYOaEG6CVVw-ZAeIJEFaNTqjSdEy6UEfUCPcEUOfwQXacU6iGKJRkcN8pneTY-AhNAoAQVhD2PKAznxS4ED+QIPCgrZYPFQJ2lhAB6IioAAAWAZgAFo6EgdEaJHcQAHF8TQX8AAtDknYF1QNeFoBTAArFVKKgFNoQ4ARBljPiIAAOiUOdYgXURDTQNcYi3MM9UHEwkJ-KBT1zTdLzNG8klktBHwGF8WQgshP2-X8OH-AigOk4YwMseycJggIEOqL9kNQrh0Mw7CNlw9B8MIkjyMomjaDo4AGO4ZjWI46A0GzXN8yiaEvw4BAwEgcQPggCAwFYYBOMMhAeBRWSFKU097ByvMC3QDTN23HS9wVfSTzPEzAivG8syMjq5ms59lh8xzkL-aA3IGECRmwNIfMivy4ICwaULQiAMKCCKfB2mLYX2KBdIoAZPDsfZFAAcj8STgCegBudUkDeobjIvYI6nuqBHqgJ6BCUtBPsMlUYjYwzhoB2d5xQ7hyr+MlzMsmaGjmj9QxuyDP1Bl6ftPJ7Mle09noQJ6AIulqFzONGDQAZVhpgcmZrh0bkhxLGkOSrvIEWwap97-l87YwW53n+YQJRZbZjm0EFiG4eR5TvtoAJMZ3HQJrPPL0Bx2zmgmRRCYch7Qme8WKe+36wdp+ndkIxntfQdnIZyPwdbQPnHEF4XRdJt66dkbbpcUP30EDywlFjtBvbhtWVaUIA

Steps to reproduce

  1. Open the TypeScript Playground link.
  2. Notice that the useQueryLoose example incorrectly accepts the wrong UseQueryResult<{ wow: string }> annotation.
  3. Notice that the useQueryFenced example correctly rejects that wrong annotation, but brokenSecond?.second fails to type-check.
  4. Notice that the useQueryDistributed example still rejects the wrong annotation and allows the discriminated-union narrowing case.

Expected behavior

query.data?.type === 'first' ? undefined : query.data should narrow to { type: 'second'; second: string } | undefined, so second?.second should type-check.

At the same time, an incorrect contextual annotation like UseQueryResult<{ wow: string }> should still be rejected when the query options produce { wow: boolean }.

How often does this bug happen?

Every time

Screenshots or Videos

No response

Platform

  • TypeScript type-checking
  • Every runtime platform

Tanstack Query adapter

React Query

TanStack Query version

5.101.2

TypeScript version

5.9.3

Additional context

A possible fix direction is a distributive wrapper:

type NarrowableNoInfer<T> = T extends unknown ? NoInfer<T> : never

The playground shows this preserving the reverse-inference guard while restoring discriminated-union narrowing.

Related context:

No activity

Activity on this issue will appear here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions