+ )
+ }
+
+ const rendered = renderWithClient(queryClient, () => (
+ loading}>
+
+
+ ))
+
+ expect(rendered.getByText('type to search')).toBeInTheDocument()
+ expect(rendered.getByLabelText('term')).toBeInTheDocument()
+ expect(queryFn).not.toHaveBeenCalled()
+
+ fireEvent.input(rendered.getByLabelText('term'), {
+ target: { value: 'solid' },
+ })
+ await vi.advanceTimersByTimeAsync(0)
+ expect(rendered.getByLabelText('term')).toBeInTheDocument()
+ expect(rendered.queryByText('loading')).not.toBeInTheDocument()
+ expect(queryFn).toHaveBeenCalledTimes(1)
await vi.advanceTimersByTimeAsync(10)
- expect(rendered.getByText('ready')).toBeInTheDocument()
+ expect(rendered.getByText('results for solid')).toBeInTheDocument()
+ expect(rendered.getByLabelText('term')).toBeInTheDocument()
})
it('exposes background fetch state reactively', async () => {
diff --git a/packages/solid-query/src/__tests__/useQuery.test-d.tsx b/packages/solid-query/src/__tests__/useQuery.test-d.tsx
index de57a931414..5c770a03f5b 100644
--- a/packages/solid-query/src/__tests__/useQuery.test-d.tsx
+++ b/packages/solid-query/src/__tests__/useQuery.test-d.tsx
@@ -1,5 +1,6 @@
import { describe, expectTypeOf, it } from 'vitest'
import { queryKey } from '@tanstack/query-test-utils'
+import { skipToken } from '@tanstack/query-core'
import { queryOptions, useQuery } from '../index'
import type { OmitKeyof, QueryFunction, UseQueryOptions } from '..'
@@ -249,6 +250,77 @@ describe('useQuery', () => {
})
})
+ describe('queries that can be disabled', () => {
+ it('TData includes undefined when enabled is a boolean', () => {
+ const { data } = useQuery(() => ({
+ queryKey: queryKey(),
+ queryFn: () => ({ wow: true }),
+ enabled: Math.random() > 0.5,
+ }))
+
+ expectTypeOf(data).toEqualTypeOf<{ wow: boolean } | undefined>()
+ })
+
+ it('TData includes undefined when enabled is a callback', () => {
+ const { data } = useQuery(() => ({
+ queryKey: queryKey(),
+ queryFn: () => ({ wow: true }),
+ enabled: (query) => query.state.dataUpdateCount === 0,
+ }))
+
+ expectTypeOf(data).toEqualTypeOf<{ wow: boolean } | undefined>()
+ })
+
+ it('TData stays defined when enabled is literally true', () => {
+ const { data } = useQuery(() => ({
+ queryKey: queryKey(),
+ queryFn: () => ({ wow: true }),
+ enabled: true as const,
+ }))
+
+ expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>()
+ })
+
+ it('TData includes undefined when queryFn may be skipToken', () => {
+ const { data } = useQuery(() => ({
+ queryKey: queryKey(),
+ queryFn: Math.random() > 0.5 ? skipToken : () => ({ wow: true }),
+ }))
+
+ expectTypeOf(data).toEqualTypeOf<{ wow: boolean } | undefined>()
+ })
+
+ it('TData stays defined when a query that can be disabled has initialData', () => {
+ const { data } = useQuery(() => ({
+ queryKey: queryKey(),
+ queryFn: () => ({ wow: true }),
+ enabled: false,
+ initialData: { wow: true },
+ }))
+
+ expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>()
+ })
+
+ it('carries through queryOptions', () => {
+ const canDisable = queryOptions({
+ queryKey: queryKey(),
+ queryFn: () => ({ wow: true }),
+ enabled: false,
+ })
+ const always = queryOptions({
+ queryKey: queryKey(),
+ queryFn: () => ({ wow: true }),
+ })
+
+ expectTypeOf(useQuery(() => canDisable).data).toEqualTypeOf<
+ { wow: boolean } | undefined
+ >()
+ expectTypeOf(useQuery(() => always).data).toEqualTypeOf<{
+ wow: boolean
+ }>()
+ })
+ })
+
describe('generic indexed access TData', () => {
// https://github.com/TanStack/query/issues/9937
it('should be assignable back to its source indexed type when passed to a generic function parameter', () => {
diff --git a/packages/solid-query/src/__tests__/useQuery.test.tsx b/packages/solid-query/src/__tests__/useQuery.test.tsx
index ed1b4405245..b0d81424579 100644
--- a/packages/solid-query/src/__tests__/useQuery.test.tsx
+++ b/packages/solid-query/src/__tests__/useQuery.test.tsx
@@ -295,7 +295,7 @@ describe('useQuery', () => {
it('should not cancel an ongoing fetch when refetch is called (cancelRefetch=true) if we do not have data yet', async () => {
const key = queryKey()
let fetchCount = 0
- let state!: UseQueryResult
+ let state!: UseQueryResult
function Page() {
state = useQuery(() => ({
@@ -671,7 +671,7 @@ describe('useQuery', () => {
it('should not update disabled query when refetch with refetchQueries', async () => {
const key = queryKey()
let count = 0
- let state!: UseQueryResult
+ let state!: UseQueryResult
function Page() {
state = useQuery(() => ({
@@ -701,7 +701,7 @@ describe('useQuery', () => {
it('should not refetch disabled query when invalidated with invalidateQueries', async () => {
const key = queryKey()
let count = 0
- let state!: UseQueryResult
+ let state!: UseQueryResult
function Page() {
state = useQuery(() => ({
@@ -732,7 +732,7 @@ describe('useQuery', () => {
const key = queryKey()
const [count, setCount] = createSignal(0)
let fetches = 0
- let state!: UseQueryResult
+ let state!: UseQueryResult
function Page() {
state = useQuery(() => ({
@@ -759,15 +759,12 @@ describe('useQuery', () => {
setCount(1)
await vi.advanceTimersByTimeAsync(10)
- // Switching to a disabled key never fetches; the committed value from the
- // previous key holds while the new (never-arriving) read stays pending.
+ // switching to a disabled key never fetches; the read is `undefined`
expect(fetches).toBe(1)
expect(state.status).toBe('pending')
expect(state.fetchStatus).toBe('idle')
+ expect(rendered.getByText('data:')).toBeInTheDocument()
- // Settle the parked read before the test ends: a transition held on a
- // never-resolving promise outlives unmount in the global reactive engine
- // and would corrupt later tests.
setCount(0)
await vi.advanceTimersByTimeAsync(10)
expect(rendered.getByText('data: 0')).toBeInTheDocument()
@@ -855,15 +852,10 @@ describe('useQuery', () => {
expect(state.isFetching).toBe(false)
})
- // The key switches park the data node on the never-resolving pending read
- // (disabled query, nothing to fetch), so the committed UI holds the
- // previous key's data through the transition. `refetch()` syncs the
- // observer to the latest computed options at call time — the deferred
- // setOptions render effect can't be relied on under a held transition.
it('should keep the previous data on disabled query when placeholderData is set and switching query key multiple times', async () => {
const key = queryKey()
const [count, setCount] = createSignal(10)
- let state!: UseQueryResult
+ let state!: UseQueryResult
queryClient.setQueryData([key, 10], 10)
@@ -890,14 +882,17 @@ describe('useQuery', () => {
setCount(11)
await vi.advanceTimersByTimeAsync(0)
expect(rendered.getByText('data: 10')).toBeInTheDocument()
+ expect(state.isPlaceholderData).toBe(true)
setCount(12)
await vi.advanceTimersByTimeAsync(0)
expect(rendered.getByText('data: 10')).toBeInTheDocument()
+ expect(state.isPlaceholderData).toBe(true)
void state.refetch()
await vi.advanceTimersByTimeAsync(10)
expect(rendered.getByText('data: 12')).toBeInTheDocument()
+ expect(state.isPlaceholderData).toBe(false)
})
it('should use the correct query function when components use different configurations', async () => {
@@ -2377,7 +2372,7 @@ describe('useQuery', () => {
const key = queryKey()
const [enabled, setEnabled] = createSignal(false)
let count = 0
- let state!: UseQueryResult
+ let state!: UseQueryResult
function Page() {
state = useQuery(() => ({
@@ -2399,8 +2394,8 @@ describe('useQuery', () => {
const rendered = renderWithClient(queryClient, () => )
- // disabled with nothing cached: the read parks in
- expect(rendered.getByText('loading')).toBeInTheDocument()
+ // disabled with nothing cached: the read is `undefined`, no suspension
+ expect(rendered.getByText('data: undefined')).toBeInTheDocument()
expect(count).toBe(0)
queryClient.prefetchQuery({
@@ -2408,7 +2403,7 @@ describe('useQuery', () => {
queryFn: () => Promise.resolve('prefetched data'),
})
await vi.advanceTimersByTimeAsync(0)
- // the cache write revives the parked read even while disabled
+ // the cache write is served even while disabled
expect(rendered.getByText('data: prefetched data')).toBeInTheDocument()
expect(count).toBe(0)
@@ -2435,9 +2430,9 @@ describe('useQuery', () => {
return (
-
FetchStatus: {query.fetchStatus}
- no data}>
-
Data: {query.data}
+
Loading: {String(query.isLoading)}
+ fallback}>
+
Data: {query.data ?? 'no data'}
)
@@ -2445,15 +2440,19 @@ describe('useQuery', () => {
const rendered = renderWithClient(queryClient, () => )
- expect(rendered.getByText('FetchStatus: idle')).toBeInTheDocument()
- expect(rendered.getByText('no data')).toBeInTheDocument()
+ expect(rendered.getByText('Loading: false')).toBeInTheDocument()
+ expect(rendered.getByText('Data: no data')).toBeInTheDocument()
+ expect(rendered.queryByText('fallback')).not.toBeInTheDocument()
setShouldFetch(true)
await vi.advanceTimersByTimeAsync(0)
- expect(rendered.getByText('FetchStatus: fetching')).toBeInTheDocument()
+ // the committed `undefined` read holds while the first fetch runs
+ expect(rendered.getByText('Loading: true')).toBeInTheDocument()
+ expect(rendered.getByText('Data: no data')).toBeInTheDocument()
+ expect(rendered.queryByText('fallback')).not.toBeInTheDocument()
await vi.advanceTimersByTimeAsync(10)
expect(rendered.getByText('Data: data')).toBeInTheDocument()
- expect(rendered.getByText('FetchStatus: idle')).toBeInTheDocument()
+ expect(rendered.getByText('Loading: false')).toBeInTheDocument()
})
// See https://github.com/TanStack/query/issues/7711
@@ -3055,7 +3054,7 @@ describe('useQuery', () => {
it('should use placeholder data even for disabled queries', async () => {
const key1 = queryKey()
const [count, setCount] = createSignal(0)
- let state!: UseQueryResult
+ let state!: UseQueryResult
function Page() {
state = useQuery(() => ({
@@ -3538,7 +3537,7 @@ describe('useQuery', () => {
it('should update query state and not refetch when resetting a disabled query with resetQueries', async () => {
const key = queryKey()
let count = 0
- let state!: UseQueryResult
+ let state!: UseQueryResult
function Page() {
state = useQuery(() => ({
@@ -3567,8 +3566,8 @@ describe('useQuery', () => {
const rendered = renderWithClient(queryClient, () => )
- // Disabled query with no data: the data read stays pending
- expect(rendered.getByText('loading')).toBeInTheDocument()
+ expect(rendered.getByText('data:')).toBeInTheDocument()
+ expect(rendered.queryByText('loading')).not.toBeInTheDocument()
expect(state.isPending).toBe(true)
expect(state.isFetching).toBe(false)
@@ -3580,17 +3579,12 @@ describe('useQuery', () => {
fireEvent.click(rendered.getByRole('button', { name: /reset/i }))
await vi.advanceTimersByTimeAsync(10)
// Resetting a disabled query does not refetch
+ expect(rendered.getByText('data:')).toBeInTheDocument()
expect(state.isPending).toBe(true)
+ expect(state.isFetching).toBe(false)
expect(state.fetchStatus).toBe('idle')
expect(count).toBe(1)
- // PORT-REVIEW (kept running): `state.isFetching` reports true here even
- // though nothing fetches — the isFetching projection ORs in a
- // "value pending" probe on the data node, and after a reset the disabled
- // query's data read is parked pending forever. Asserting via fetchStatus
- // (which correctly reads 'idle') instead. See port-notes/useQuery.md.
-
- // Settle the parked pending read before the test ends (a never-resolving
- // read held past unmount corrupts the global reactive engine).
+
fireEvent.click(rendered.getByRole('button', { name: /refetch/i }))
await vi.advanceTimersByTimeAsync(10)
expect(rendered.getByText('data: 2')).toBeInTheDocument()
diff --git a/packages/solid-query/src/index.ts b/packages/solid-query/src/index.ts
index 4325d5aa157..7dfc6f04cbd 100644
--- a/packages/solid-query/src/index.ts
+++ b/packages/solid-query/src/index.ts
@@ -35,6 +35,7 @@ export type {
export { useQuery } from './useQuery'
export { queryOptions } from './queryOptions'
export type {
+ AlwaysEnabledOptions,
DefinedInitialDataOptions,
UndefinedInitialDataOptions,
} from './queryOptions'
@@ -50,6 +51,7 @@ export { useIsFetching } from './useIsFetching'
export { useInfiniteQuery } from './useInfiniteQuery'
export { infiniteQueryOptions } from './infiniteQueryOptions'
export type {
+ AlwaysEnabledInfiniteOptions,
DefinedInitialDataInfiniteOptions,
UndefinedInitialDataInfiniteOptions,
} from './infiniteQueryOptions'
diff --git a/packages/solid-query/src/infiniteQueryOptions.ts b/packages/solid-query/src/infiniteQueryOptions.ts
index 6f4885d999f..7d45d417682 100644
--- a/packages/solid-query/src/infiniteQueryOptions.ts
+++ b/packages/solid-query/src/infiniteQueryOptions.ts
@@ -3,11 +3,28 @@ import type {
DefaultError,
InfiniteData,
NonUndefinedGuard,
+ QueryFunction,
QueryKey,
} from '@tanstack/query-core'
import type { InfiniteQueryOptions } from './types'
import type { Accessor } from 'solid-js'
+/** Options that cannot disable the query and carry no `initialData`: `data` is `TData`. */
+export type AlwaysEnabledInfiniteOptions<
+ TQueryFnData,
+ TError = DefaultError,
+ TData = InfiniteData,
+ TQueryKey extends QueryKey = QueryKey,
+ TPageParam = unknown,
+> = Accessor<
+ InfiniteQueryOptions & {
+ initialData?: undefined
+ enabled?: true
+ queryFn?: QueryFunction
+ }
+>
+
+/** Options without `initialData` that may disable the query: `data` is `TData | undefined`. */
export type UndefinedInitialDataInfiniteOptions<
TQueryFnData,
TError = DefaultError,
@@ -34,6 +51,33 @@ export type DefinedInitialDataInfiniteOptions<
| (() => NonUndefinedGuard>)
}
>
+export function infiniteQueryOptions<
+ TQueryFnData,
+ TError = DefaultError,
+ TData = InfiniteData,
+ TQueryKey extends QueryKey = QueryKey,
+ TPageParam = unknown,
+>(
+ options: ReturnType<
+ AlwaysEnabledInfiniteOptions<
+ TQueryFnData,
+ TError,
+ TData,
+ TQueryKey,
+ TPageParam
+ >
+ >,
+): ReturnType<
+ AlwaysEnabledInfiniteOptions<
+ TQueryFnData,
+ TError,
+ TData,
+ TQueryKey,
+ TPageParam
+ >
+> & {
+ queryKey: DataTag>
+}
export function infiniteQueryOptions<
TQueryFnData,
TError = DefaultError,
diff --git a/packages/solid-query/src/queryOptions.ts b/packages/solid-query/src/queryOptions.ts
index fea9db1e1f3..f00786febdf 100644
--- a/packages/solid-query/src/queryOptions.ts
+++ b/packages/solid-query/src/queryOptions.ts
@@ -1,7 +1,27 @@
-import type { DataTag, DefaultError, QueryKey } from '@tanstack/query-core'
+import type {
+ DataTag,
+ DefaultError,
+ QueryFunction,
+ QueryKey,
+} from '@tanstack/query-core'
import type { QueryOptions } from './types'
import type { Accessor } from 'solid-js'
+/** Options that cannot disable the query and carry no `initialData`: `data` is `TData`. */
+export type AlwaysEnabledOptions<
+ TQueryFnData = unknown,
+ TError = DefaultError,
+ TData = TQueryFnData,
+ TQueryKey extends QueryKey = QueryKey,
+> = Accessor<
+ QueryOptions & {
+ initialData?: undefined
+ enabled?: true
+ queryFn?: QueryFunction
+ }
+>
+
+/** Options without `initialData` that may disable the query: `data` is `TData | undefined`. */
export type UndefinedInitialDataOptions<
TQueryFnData = unknown,
TError = DefaultError,
@@ -24,6 +44,19 @@ export type DefinedInitialDataOptions<
}
>
+export function queryOptions<
+ TQueryFnData = unknown,
+ TError = DefaultError,
+ TData = TQueryFnData,
+ TQueryKey extends QueryKey = QueryKey,
+>(
+ options: ReturnType<
+ AlwaysEnabledOptions
+ >,
+): ReturnType> & {
+ queryKey: DataTag
+}
+
export function queryOptions<
TQueryFnData = unknown,
TError = DefaultError,
diff --git a/packages/solid-query/src/types.ts b/packages/solid-query/src/types.ts
index 927af03e543..98561f6c372 100644
--- a/packages/solid-query/src/types.ts
+++ b/packages/solid-query/src/types.ts
@@ -70,12 +70,13 @@ export type UseQueryOptions<
/* --- Create Query and Create Base Query Types --- */
/**
- * `data` is non-optional: it is a suspending async read. It never returns
- * `undefined` — a read either suspends into the nearest ``
- * boundary (first fetch in flight, disabled, restoring), returns a value
+ * `data` is a suspending async read: a read either suspends into the nearest
+ * `` boundary (first fetch in flight, restoring), returns a value
* (committed, placeholder, initial), or throws (`` /
- * `throwOnError`). The v5 `TData | undefined` face existed because reads
- * could observe the pre-fetch gap; here that gap is suspension.
+ * `throwOnError`), so it is `TData` rather than the v5 `TData | undefined`.
+ * The exception is a disabled query with nothing cached, which reads
+ * `undefined` without suspending; the hook overloads widen `data` for
+ * options that can disable the query (`enabled`, `skipToken`).
*/
export type UseBaseQueryResult<
TData = unknown,
diff --git a/packages/solid-query/src/useBaseQuery.ts b/packages/solid-query/src/useBaseQuery.ts
index d152a685349..82848d23575 100644
--- a/packages/solid-query/src/useBaseQuery.ts
+++ b/packages/solid-query/src/useBaseQuery.ts
@@ -28,12 +28,10 @@ import type {
const isServer = typeof window === 'undefined'
/**
- * A read of a pending-idle query (disabled, or reset with nothing in flight)
- * has no value and nothing to wait on. Parking the reader on a promise that
- * never resolves suspends it into the nearest `` boundary until the
- * query actually starts fetching (enabling it, a refetch, a cache write) —
- * at which point the version bump re-runs the compute and the superseded
- * in-flight is ignored by the engine.
+ * Parks a read whose answer is on its way through a channel other than a
+ * fetch this compute could start (hydration adoption, node priming, a
+ * persister restore). Each of those lands by re-running the tracked compute,
+ * so the promise itself never needs to settle.
*/
const NEVER: Promise = new Promise(noop)
@@ -377,20 +375,34 @@ export function useBaseQueryLayer<
return enabled !== false
}
+ let servedQuery:
+ | Query
+ | undefined
+
const resolvePlaceholder = (
opts: ReturnType,
+ previous?: Query,
): TQueryData | undefined => {
const placeholder = opts.placeholderData
if (placeholder === undefined) return undefined
- // The function form receives previous data/query in React Query as a
- // `keepPreviousData` vehicle. Solid 2 holds the previous committed value
- // natively while a new promise is pending, so previous-data plumbing is
- // unnecessary here; a function placeholder computes from nothing.
+ // The function form is React Query's `keepPreviousData` vehicle. While a
+ // fetch is pending, Solid 2 holds the previous committed value natively,
+ // so no `previous` is passed. A disabled query has no pending fetch to
+ // hold against, so there the previously served query is supplied.
return typeof placeholder === 'function'
- ? (placeholder as () => TQueryData | undefined)()
+ ? (
+ placeholder as (
+ previousData: TQueryData | undefined,
+ previousQuery:
+ | Query
+ | undefined,
+ ) => TQueryData | undefined
+ )(previous?.state.data, previous)
: placeholder
}
+ const placeholderPrevious = () => (isEnabled() ? undefined : servedQuery)
+
/**
* The data compute. It returns either the settled `{ value }` root or a
* promise of it — the engine does the rest: pending reads suspend into
@@ -431,11 +443,15 @@ export function useBaseQueryLayer<
const wrap = (d: any): { value: TData } => ({
value: select ? select(d as TQueryData) : (d as TData),
})
+ const serve = (d: any): { value: TData } => {
+ servedQuery = q
+ return wrap(d)
+ }
// Placeholder: show immediately instead of suspending while the first
// fetch runs. When the fetch lands the version bump swaps in real data.
if (state.data === undefined && state.status === 'pending') {
- const placeholder = resolvePlaceholder(opts)
+ const placeholder = resolvePlaceholder(opts, placeholderPrevious())
if (placeholder !== undefined) return wrap(placeholder)
}
@@ -453,7 +469,7 @@ export function useBaseQueryLayer<
if (state.fetchStatus !== 'idle') {
if (state.data === undefined || prev !== undefined) {
const promise = q.promise
- if (promise) return chainOnce(promise, select, wrap)
+ if (promise) return chainOnce(promise, select, serve)
}
}
@@ -472,16 +488,16 @@ export function useBaseQueryLayer<
}
}
- if (state.data !== undefined) return wrap(state.data)
+ if (state.data !== undefined) return serve(state.data)
// Pending-idle: nothing in flight, nothing cached. If the query is
// enabled, the tracked read itself starts the fetch (the router
// `query()` model — reads pull the async). This is not just the server
- // path: on the client it is what revives a query whose enabling change
- // arrives while the subtree is parked under a suspended boundary —
- // parked boundaries hold effects, so the observer's option-driven fetch
- // can never fire there, but computes still re-run. `q.fetch` dedupes
- // against any fetch the observer already started.
+ // path: on the client it is what starts the fetch for a query whose
+ // enabling change arrives while the subtree sits under a suspended
+ // boundary — suspended boundaries hold effects, so the observer's
+ // option-driven fetch can never fire there, but computes still re-run.
+ // `q.fetch` dedupes against any fetch the observer already started.
if (isEnabled()) {
/**
* Never start a fetch inside the hydration window. Adoption
@@ -529,9 +545,12 @@ export function useBaseQueryLayer<
* sees the identical options object — a no-op diff.
*/
if (!isServer) observer.setOptions(opts as any)
- return chainOnce(q.fetch(opts as any), select, wrap)
+ return chainOnce(q.fetch(opts as any), select, serve)
}
- return NEVER
+ // Disabled with nothing cached: there is no answer and nothing will
+ // produce one, so the read is `undefined` rather than a suspension that
+ // could never settle.
+ return { value: undefined as TData }
}
/**
@@ -636,7 +655,9 @@ export function useBaseQueryLayer<
const hasPlaceholder = () =>
meta.status === 'pending' &&
- untrack(() => resolvePlaceholder(defaultedOptions())) !== undefined
+ untrack(() =>
+ resolvePlaceholder(defaultedOptions(), placeholderPrevious()),
+ ) !== undefined
const status = () => (hasPlaceholder() ? 'success' : meta.status)
const isPending = () => status() === 'pending'
/**
diff --git a/packages/solid-query/src/useInfiniteQuery.ts b/packages/solid-query/src/useInfiniteQuery.ts
index 22a0bab33f1..6afe68597a5 100644
--- a/packages/solid-query/src/useInfiniteQuery.ts
+++ b/packages/solid-query/src/useInfiniteQuery.ts
@@ -18,6 +18,7 @@ import type {
} from './types'
import type { Accessor } from 'solid-js'
import type {
+ AlwaysEnabledInfiniteOptions,
DefinedInitialDataInfiniteOptions,
UndefinedInitialDataInfiniteOptions,
} from './infiniteQueryOptions'
@@ -97,7 +98,7 @@ export function useInfiniteQuery<
TQueryKey extends QueryKey = QueryKey,
TPageParam = unknown,
>(
- options: UndefinedInitialDataInfiniteOptions<
+ options: AlwaysEnabledInfiniteOptions<
TQueryFnData,
TError,
TData,
@@ -106,6 +107,22 @@ export function useInfiniteQuery<
>,
queryClient?: Accessor,
): UseInfiniteQueryResult
+export function useInfiniteQuery<
+ TQueryFnData,
+ TError = DefaultError,
+ TData = InfiniteData,
+ TQueryKey extends QueryKey = QueryKey,
+ TPageParam = unknown,
+>(
+ options: UndefinedInitialDataInfiniteOptions<
+ TQueryFnData,
+ TError,
+ TData,
+ TQueryKey,
+ TPageParam
+ >,
+ queryClient?: Accessor,
+): UseInfiniteQueryResult
export function useInfiniteQuery<
TQueryFnData,
diff --git a/packages/solid-query/src/useQueries.ts b/packages/solid-query/src/useQueries.ts
index 3c93ed6bd26..b42dbb17735 100644
--- a/packages/solid-query/src/useQueries.ts
+++ b/packages/solid-query/src/useQueries.ts
@@ -40,6 +40,30 @@ type MAXIMUM_DEPTH = 20
// Widen the type of the symbol to enable type inference even if skipToken is not immutable.
type SkipTokenForUseQueries = symbol
+type HasDefinedInitialData = 'initialData' extends keyof T
+ ? {} extends Pick
+ ? false
+ : undefined extends T['initialData' & keyof T]
+ ? false
+ : true
+ : false
+
+// `TData | undefined` when the entry can disable its query (same rule as the `useQuery` overloads).
+type DataFor =
+ HasDefinedInitialData extends true
+ ? TData
+ : 'enabled' extends keyof T
+ ? Exclude extends true
+ ? TData
+ : TData | undefined
+ : 'queryFn' extends keyof T
+ ? [
+ Extract, symbol>,
+ ] extends [never]
+ ? TData
+ : TData | undefined
+ : TData
+
type GetOptions =
// Part 1: responsible for applying explicit type parameter to function arguments, if object { queryFnData: TQueryFnData, error: TError, data: TData }
T extends {
@@ -100,7 +124,7 @@ type GetResults =
throwOnError?: ThrowOnError
}
? UseQueryResult<
- unknown extends TData ? TQueryFnData : TData,
+ DataFor,
unknown extends TError ? DefaultError : TError
>
: // Fallback
@@ -187,7 +211,8 @@ export function useQueries<
* whose options stay reactive through the per-index accessor — option
* changes flow into the existing row without tearing it down, while
* length changes create/dispose tail rows. Every result has identical
- * semantics to `useQuery`: reads suspend, settled data is non-nullable.
+ * semantics to `useQuery`: reads suspend while a fetch is in flight, a
+ * disabled entry with nothing cached reads `undefined`.
*/
const length = createMemo(() => queriesOptions().queries.length)
const results = repeat(length, (index) => {
diff --git a/packages/solid-query/src/useQuery.ts b/packages/solid-query/src/useQuery.ts
index e8efb4796a9..52c9c1dad72 100644
--- a/packages/solid-query/src/useQuery.ts
+++ b/packages/solid-query/src/useQuery.ts
@@ -10,6 +10,7 @@ import type {
UseQueryResult,
} from './types'
import type {
+ AlwaysEnabledOptions,
DefinedInitialDataOptions,
UndefinedInitialDataOptions,
} from './queryOptions'
@@ -20,7 +21,17 @@ export function useQuery<
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
- options: UndefinedInitialDataOptions,
+ options: DefinedInitialDataOptions,
+ queryClient?: () => QueryClient,
+): DefinedUseQueryResult
+
+export function useQuery<
+ TQueryFnData = unknown,
+ TError = DefaultError,
+ TData = TQueryFnData,
+ TQueryKey extends QueryKey = QueryKey,
+>(
+ options: AlwaysEnabledOptions,
queryClient?: () => QueryClient,
): UseQueryResult
@@ -30,9 +41,9 @@ export function useQuery<
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
- options: DefinedInitialDataOptions,
+ options: UndefinedInitialDataOptions,
queryClient?: () => QueryClient,
-): DefinedUseQueryResult
+): UseQueryResult
export function useQuery<
TQueryFnData,
TError = DefaultError,