>()
})
})
diff --git a/packages/solid-query/src/__tests__/useInfiniteQuery.test.tsx b/packages/solid-query/src/__tests__/useInfiniteQuery.test.tsx
index 939b30ef8f..d3548b744c 100644
--- a/packages/solid-query/src/__tests__/useInfiniteQuery.test.tsx
+++ b/packages/solid-query/src/__tests__/useInfiniteQuery.test.tsx
@@ -8,7 +8,9 @@ import {
Switch,
createRenderEffect,
createSignal,
+ deep,
snapshot,
+ untrack,
} from 'solid-js'
import { queryKey, sleep } from '@tanstack/query-test-utils'
import {
@@ -63,7 +65,7 @@ describe('useInfiniteQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -216,18 +218,10 @@ describe('useInfiniteQuery', () => {
}))
createRenderEffect(
- () => ({
- data: state.data,
- isFetching: state.isFetching,
- isFetchingNextPage: state.isFetchingNextPage,
- isSuccess: state.isSuccess,
- isPlaceholderData: state.isPlaceholderData,
- }),
+ () => deep(state),
() => {
states.push({
- data: state.data
- ? JSON.parse(JSON.stringify(state.data))
- : undefined,
+ data: state.data,
isFetching: state.isFetching,
isFetchingNextPage: state.isFetchingNextPage,
isSuccess: state.isSuccess,
@@ -240,7 +234,7 @@ describe('useInfiniteQuery', () => {
state.fetchNextPage()}>fetchNextPage
setOrder('asc')}>order
-
data: {state.data?.pages.join(',') ?? 'null'}
+
data: {state.data.pages.join(',')}
isFetching: {String(state.isFetching)}
)
@@ -330,7 +324,7 @@ describe('useInfiniteQuery', () => {
createRenderEffect(
() => {
renderCount++
- return { status: state.status, data: state.data }
+ return { status: state.status, dataUpdatedAt: state.dataUpdatedAt }
},
() => {
states.push(snapshot(state) as any)
@@ -382,7 +376,7 @@ describe('useInfiniteQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
(s) => {
states.push(s)
},
@@ -430,12 +424,10 @@ describe('useInfiniteQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push({
- data: state.data
- ? JSON.parse(JSON.stringify(state.data))
- : undefined,
+ data: state.data,
isSuccess: state.isSuccess,
})
},
@@ -444,7 +436,7 @@ describe('useInfiniteQuery', () => {
return (
state.fetchNextPage()}>fetchNextPage
-
data: {state.data?.pages.join(',') ?? 'null'}
+
data: {state.data.pages.join(',')}
isFetching: {state.isFetching}
)
@@ -500,7 +492,7 @@ describe('useInfiniteQuery', () => {
createRenderEffect(
() => ({
- data: state.data,
+ dataUpdatedAt: state.dataUpdatedAt,
hasNextPage: state.hasNextPage,
hasPreviousPage: state.hasPreviousPage,
isFetching: state.isFetching,
@@ -510,9 +502,7 @@ describe('useInfiniteQuery', () => {
}),
() => {
states.push({
- data: state.data
- ? JSON.parse(JSON.stringify(state.data))
- : undefined,
+ data: state.data,
hasNextPage: state.hasNextPage,
hasPreviousPage: state.hasPreviousPage,
isFetching: state.isFetching,
@@ -594,7 +584,7 @@ describe('useInfiniteQuery', () => {
createRenderEffect(
() => ({
- data: state.data,
+ dataUpdatedAt: state.dataUpdatedAt,
isFetching: state.isFetching,
isFetchingNextPage: state.isFetchingNextPage,
isRefetching: state.isRefetching,
@@ -602,9 +592,7 @@ describe('useInfiniteQuery', () => {
}),
() => {
states.push({
- data: state.data
- ? JSON.parse(JSON.stringify(state.data))
- : undefined,
+ data: state.data,
isFetching: state.isFetching,
isFetchingNextPage: state.isFetchingNextPage,
isRefetching: state.isRefetching,
@@ -620,7 +608,7 @@ describe('useInfiniteQuery', () => {
fetchPreviousPage
state.refetch()}>refetch
- data: {state.data?.pages.join(',') ?? 'null'}
+ data: {state.data.pages.join(',')}
isFetching: {String(state.isFetching)}
)
@@ -736,7 +724,7 @@ describe('useInfiniteQuery', () => {
createRenderEffect(
() => ({
- data: state.data,
+ dataUpdatedAt: state.dataUpdatedAt,
isFetching: state.isFetching,
isFetchNextPageError: state.isFetchNextPageError,
isFetchingNextPage: state.isFetchingNextPage,
@@ -747,9 +735,7 @@ describe('useInfiniteQuery', () => {
}),
() => {
states.push({
- data: state.data
- ? JSON.parse(JSON.stringify(state.data))
- : undefined,
+ data: state.data,
isFetching: state.isFetching,
isFetchNextPageError: state.isFetchNextPageError,
isFetchingNextPage: state.isFetchingNextPage,
@@ -771,7 +757,7 @@ describe('useInfiniteQuery', () => {
>
refetch
- data: {state.data?.pages.join(',') ?? 'null'}
+ data: {state.data.pages.join(',')}
isFetching: {String(state.isFetching)}
)
@@ -859,7 +845,7 @@ describe('useInfiniteQuery', () => {
createRenderEffect(
() => ({
- data: state.data,
+ dataUpdatedAt: state.dataUpdatedAt,
isFetching: state.isFetching,
isFetchNextPageError: state.isFetchNextPageError,
isFetchingNextPage: state.isFetchingNextPage,
@@ -870,9 +856,7 @@ describe('useInfiniteQuery', () => {
}),
() => {
states.push({
- data: state.data
- ? JSON.parse(JSON.stringify(state.data))
- : undefined,
+ data: state.data,
isFetching: state.isFetching,
isFetchNextPageError: state.isFetchNextPageError,
isFetchingNextPage: state.isFetchingNextPage,
@@ -887,7 +871,7 @@ describe('useInfiniteQuery', () => {
return (
state.fetchNextPage()}>fetchNextPage
-
data: {state.data?.pages.join(',') ?? 'null'}
+
data: {state.data.pages.join(',')}
isFetching: {String(state.isFetching)}
)
@@ -975,7 +959,7 @@ describe('useInfiniteQuery', () => {
createRenderEffect(
() => ({
- data: state.data,
+ dataUpdatedAt: state.dataUpdatedAt,
isFetching: state.isFetching,
isFetchNextPageError: state.isFetchNextPageError,
isFetchingNextPage: state.isFetchingNextPage,
@@ -986,9 +970,7 @@ describe('useInfiniteQuery', () => {
}),
() => {
states.push({
- data: state.data
- ? JSON.parse(JSON.stringify(state.data))
- : undefined,
+ data: state.data,
isFetching: state.isFetching,
isFetchNextPageError: state.isFetchNextPageError,
isFetchingNextPage: state.isFetchingNextPage,
@@ -1005,7 +987,7 @@ describe('useInfiniteQuery', () => {
state.fetchPreviousPage()}>
fetchPreviousPage
- data: {state.data?.pages.join(',') ?? 'null'}
+ data: {state.data.pages.join(',')}
isFetching: {String(state.isFetching)}
)
@@ -1090,8 +1072,9 @@ describe('useInfiniteQuery', () => {
createRenderEffect(
() => ({
+ data: untrack(() => state.data),
hasNextPage: state.hasNextPage,
- data: state.data ? JSON.parse(JSON.stringify(state.data)) : undefined,
+ dataUpdatedAt: state.dataUpdatedAt,
isFetching: state.isFetching,
isFetchingNextPage: state.isFetchingNextPage,
isSuccess: state.isSuccess,
@@ -1319,7 +1302,7 @@ describe('useInfiniteQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -1426,7 +1409,7 @@ describe('useInfiniteQuery', () => {
createRenderEffect(
() => ({
hasNextPage: state.hasNextPage,
- data: state.data,
+ dataUpdatedAt: state.dataUpdatedAt,
isFetching: state.isFetching,
isFetchingNextPage: state.isFetchingNextPage,
isSuccess: state.isSuccess,
@@ -1434,9 +1417,7 @@ describe('useInfiniteQuery', () => {
() => {
states.push({
hasNextPage: state.hasNextPage,
- data: state.data
- ? JSON.parse(JSON.stringify(state.data))
- : undefined,
+ data: state.data,
isFetching: state.isFetching,
isFetchingNextPage: state.isFetchingNextPage,
isSuccess: state.isSuccess,
@@ -1524,7 +1505,7 @@ describe('useInfiniteQuery', () => {
createRenderEffect(
() => ({
hasNextPage: state.hasNextPage,
- data: state.data,
+ dataUpdatedAt: state.dataUpdatedAt,
isFetching: state.isFetching,
isFetchingNextPage: state.isFetchingNextPage,
isSuccess: state.isSuccess,
@@ -1599,7 +1580,7 @@ describe('useInfiniteQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -1647,7 +1628,7 @@ describe('useInfiniteQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -1695,7 +1676,7 @@ describe('useInfiniteQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -1746,7 +1727,7 @@ describe('useInfiniteQuery', () => {
return (
-
data: {state.data?.pages.join(',') ?? 'null'}
+
data: {state.data.pages.join(',')}
hasNextPage: {state.hasNextPage ? 'true' : 'false'}
)
@@ -1796,7 +1777,7 @@ describe('useInfiniteQuery', () => {
fallback={
<>
Data:
-
+
{(page, i) => (
@@ -1941,7 +1922,7 @@ describe('useInfiniteQuery', () => {
fallback={
<>
Data:
-
+
{(page, i) => (
@@ -2088,7 +2069,7 @@ describe('useInfiniteQuery', () => {
)
return (
-
Status: {state.data?.pages[0]}
+ Status: {state.data.pages[0]}
)
}
@@ -2120,7 +2101,7 @@ describe('useInfiniteQuery', () => {
)
return (
-
Status: {state.data?.pages[0]}
+ Status: {state.data.pages[0]}
)
}
diff --git a/packages/solid-query/src/__tests__/useQueries.test-d.tsx b/packages/solid-query/src/__tests__/useQueries.test-d.tsx
index eaa0a42d45..617ec56b36 100644
--- a/packages/solid-query/src/__tests__/useQueries.test-d.tsx
+++ b/packages/solid-query/src/__tests__/useQueries.test-d.tsx
@@ -5,14 +5,17 @@ import { queryOptions, useQueries } from '..'
import { QueryClient } from '../QueryClient'
import type * as QueryCore from '@tanstack/query-core'
import type { OmitKeyof } from '@tanstack/query-core'
-import type {
- QueryFunction,
- QueryFunctionContext,
- QueryKey,
- UseQueryResult,
-} from '..'
+import type { QueryFunction, QueryFunctionContext, QueryKey } from '..'
import type { QueryOptions } from '../types'
+// useQueries results are a plain reactive store with no resource backing
+// (reads never suspend), so unlike useQuery its `data` stays nullable —
+// assert against the raw query-core observer result here.
+type UseQueryResult<
+ TData = unknown,
+ TError = QueryCore.DefaultError,
+> = QueryCore.QueryObserverResult
+
describe('useQueries', () => {
it('TData should have undefined in the union even when initialData is provided as an object', () => {
const query1 = {
diff --git a/packages/solid-query/src/__tests__/useQuery.test-d.tsx b/packages/solid-query/src/__tests__/useQuery.test-d.tsx
index 278f4710b8..f37b7caa0b 100644
--- a/packages/solid-query/src/__tests__/useQuery.test-d.tsx
+++ b/packages/solid-query/src/__tests__/useQuery.test-d.tsx
@@ -16,7 +16,7 @@ describe('useQuery', () => {
queryKey: key,
queryFn: () => 'test',
}))
- expectTypeOf(fromQueryFn.data).toEqualTypeOf()
+ expectTypeOf(fromQueryFn.data).toEqualTypeOf()
expectTypeOf(fromQueryFn.error).toEqualTypeOf()
// it should be possible to specify the result type
@@ -24,7 +24,7 @@ describe('useQuery', () => {
queryKey: key,
queryFn: () => 'test',
}))
- expectTypeOf(withResult.data).toEqualTypeOf()
+ expectTypeOf(withResult.data).toEqualTypeOf()
expectTypeOf(withResult.error).toEqualTypeOf()
// it should be possible to specify the error type
@@ -32,7 +32,7 @@ describe('useQuery', () => {
queryKey: key,
queryFn: () => 'test',
}))
- expectTypeOf(withError.data).toEqualTypeOf()
+ expectTypeOf(withError.data).toEqualTypeOf()
expectTypeOf(withError.error).toEqualTypeOf()
// it should provide the result type in the configuration
@@ -46,12 +46,12 @@ describe('useQuery', () => {
queryKey: key,
queryFn: () => (Math.random() > 0.5 ? ('a' as const) : ('b' as const)),
}))
- expectTypeOf(unionTypeSync.data).toEqualTypeOf<'a' | 'b' | undefined>()
+ expectTypeOf(unionTypeSync.data).toEqualTypeOf<'a' | 'b'>()
const unionTypeAsync = useQuery<'a' | 'b'>(() => ({
queryKey: key,
queryFn: () => Promise.resolve(Math.random() > 0.5 ? 'a' : 'b'),
}))
- expectTypeOf(unionTypeAsync.data).toEqualTypeOf<'a' | 'b' | undefined>()
+ expectTypeOf(unionTypeAsync.data).toEqualTypeOf<'a' | 'b'>()
// should error when the query function result does not match with the specified type
// @ts-expect-error
@@ -66,16 +66,14 @@ describe('useQuery', () => {
queryKey: key,
queryFn: () => queryFn(),
}))
- expectTypeOf(fromGenericQueryFn.data).toEqualTypeOf()
+ expectTypeOf(fromGenericQueryFn.data).toEqualTypeOf()
expectTypeOf(fromGenericQueryFn.error).toEqualTypeOf()
const fromGenericOptionsQueryFn = useQuery(() => ({
queryKey: key,
queryFn: () => queryFn(),
}))
- expectTypeOf(fromGenericOptionsQueryFn.data).toEqualTypeOf<
- string | undefined
- >()
+ expectTypeOf(fromGenericOptionsQueryFn.data).toEqualTypeOf()
expectTypeOf(fromGenericOptionsQueryFn.error).toEqualTypeOf()
type MyData = number
@@ -133,7 +131,7 @@ describe('useQuery', () => {
...options,
}))
const test = useWrappedQuery([''], () => Promise.resolve('1'))
- expectTypeOf(test.data).toEqualTypeOf()
+ expectTypeOf(test.data).toEqualTypeOf()
// handles wrapped queries with custom fetcher passed directly to useQuery
const useWrappedFuncStyleQuery = <
@@ -153,7 +151,7 @@ describe('useQuery', () => {
const testFuncStyle = useWrappedFuncStyleQuery([''], () =>
Promise.resolve(true),
)
- expectTypeOf(testFuncStyle.data).toEqualTypeOf()
+ expectTypeOf(testFuncStyle.data).toEqualTypeOf()
describe('initialData', () => {
describe('Config object overload', () => {
@@ -188,16 +186,20 @@ describe('useQuery', () => {
expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>()
})
- it('TData should have undefined in the union when initialData is NOT provided', () => {
+ it('TData should be non-nullable even when initialData is NOT provided (reads suspend until data is ready)', () => {
const { data } = useQuery(() => ({
queryKey: queryKey(),
queryFn: () => ({ wow: true }),
}))
- expectTypeOf(data).toEqualTypeOf<{ wow: boolean } | undefined>()
+ expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>()
})
it('TData should have undefined in the union when initialData is provided as a function which can return undefined', () => {
+ // The maybe-undefined initialData function infers
+ // TData = { wow: boolean } | undefined through the defined-initialData
+ // overload, so the undefined here comes from TData itself — not from
+ // the (suspending, non-nullable) result wrapper.
const { data } = useQuery(() => ({
queryKey: queryKey(),
queryFn: () => ({ wow: true }),
@@ -219,13 +221,13 @@ describe('useQuery', () => {
expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>()
})
- it('TData should have undefined in the union when initialData is NOT provided', () => {
+ it('TData should be non-nullable even when initialData is NOT provided (reads suspend until data is ready)', () => {
const { data } = useQuery(() => ({
queryKey: queryKey(),
queryFn: () => ({ wow: true }),
}))
- expectTypeOf(data).toEqualTypeOf<{ wow: boolean } | undefined>()
+ expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>()
})
})
@@ -240,13 +242,13 @@ describe('useQuery', () => {
expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>()
})
- it('TData should have undefined in the union when initialData is NOT provided', () => {
+ it('TData should be non-nullable even when initialData is NOT provided (reads suspend until data is ready)', () => {
const { data } = useQuery(() => ({
queryKey: queryKey(),
queryFn: () => ({ wow: true }),
}))
- expectTypeOf(data).toEqualTypeOf<{ wow: boolean } | undefined>()
+ expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>()
})
})
})
@@ -292,7 +294,7 @@ describe('useQuery', () => {
// Regression guard: this call must compile. With the previous
// hand-rolled NoInfer, `data` failed to flow back into the generic
// indexed-access parameter `DataTypeToEntity[TDataType]`.
- return data ? getLabel(props.dataType, data) : null
+ return getLabel(props.dataType, data)
}
expectTypeOf(Test).toBeFunction()
diff --git a/packages/solid-query/src/__tests__/useQuery.test.tsx b/packages/solid-query/src/__tests__/useQuery.test.tsx
index 1b43f61a4a..cc2127ff0a 100644
--- a/packages/solid-query/src/__tests__/useQuery.test.tsx
+++ b/packages/solid-query/src/__tests__/useQuery.test.tsx
@@ -17,6 +17,7 @@ import {
createRenderEffect,
createSignal,
createTrackedEffect,
+ deep,
reconcile,
snapshot,
untrack,
@@ -66,7 +67,7 @@ describe('useQuery', () => {
return (
-
{state.data ?? 'default'}
+ {state.isPending ? 'default' : state.data}
)
}
@@ -95,7 +96,7 @@ describe('useQuery', () => {
createRenderEffect(
() => ({
status: state.status,
- data: state.data,
+ dataUpdatedAt: state.dataUpdatedAt,
isFetching: state.isFetching,
}),
() => {
@@ -104,10 +105,12 @@ describe('useQuery', () => {
)
if (state.isPending) {
- expectTypeOf(state.data).toEqualTypeOf()
+ // `data` is typed non-nullable in every status variant: reads
+ // suspend to the nearest Loading boundary until the value exists.
+ expectTypeOf(state.data).toEqualTypeOf()
expectTypeOf(state.error).toEqualTypeOf()
} else if (state.isLoadingError) {
- expectTypeOf(state.data).toEqualTypeOf()
+ expectTypeOf(state.data).toEqualTypeOf()
expectTypeOf(state.error).toEqualTypeOf()
} else {
expectTypeOf(state.data).toEqualTypeOf()
@@ -343,7 +346,7 @@ describe('useQuery', () => {
queryFn: () => sleep(10).then(() => 'data'),
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -499,7 +502,7 @@ describe('useQuery', () => {
function Page() {
const state = useQuery(() => ({ queryKey: key }))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -549,7 +552,7 @@ describe('useQuery', () => {
gcTime: 0,
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -613,7 +616,7 @@ describe('useQuery', () => {
refetchOnMount: false,
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -647,7 +650,7 @@ describe('useQuery', () => {
refetchOnMount: false,
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -678,7 +681,7 @@ describe('useQuery', () => {
select: (data) => data.name,
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -710,7 +713,7 @@ describe('useQuery', () => {
select: (data) => data.name,
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -742,7 +745,7 @@ describe('useQuery', () => {
select: (data) => data.name,
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -776,7 +779,7 @@ describe('useQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -818,7 +821,11 @@ describe('useQuery', () => {
},
}))
createRenderEffect(
- () => ({ status: state.status, data: state.data, error: state.error }),
+ () => ({
+ status: state.status,
+ dataUpdatedAt: state.dataUpdatedAt,
+ error: state.error,
+ }),
() => {
const s = snapshot(state)
if (s.status === 'pending')
@@ -855,7 +862,7 @@ describe('useQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -873,7 +880,7 @@ describe('useQuery', () => {
return (
-
{state.data ?? null}
+ {state.data}
)
}
@@ -904,7 +911,7 @@ describe('useQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -951,9 +958,7 @@ describe('useQuery', () => {
}))
createTrackedEffect(() => {
- if (state.data) {
- states.push(state.data)
- }
+ states.push(state.data)
})
const refetch = untrack(() => state.refetch)
@@ -1030,12 +1035,12 @@ describe('useQuery', () => {
createRenderEffect(
() => ({
status: state.status,
- data: state.data,
+ dataUpdatedAt: state.dataUpdatedAt,
isFetching: state.isFetching,
}),
() => {
- snapshots.push(state.data ? snapshot(state.data) : undefined)
- if (state.data) {
+ snapshots.push(snapshot(state.data))
+ if (state.isSuccess) {
itemRefs.push({ item0: state.data[0], item1: state.data[1] })
}
},
@@ -1046,7 +1051,7 @@ describe('useQuery', () => {
return (
refetch()}>refetch
- data: {String(state.data?.[1]?.done)}
+ data: {String(state.data[1]?.done)}
)
}
@@ -1150,7 +1155,7 @@ describe('useQuery', () => {
createRenderEffect(
() => ({
status: state.status,
- data: state.data,
+ dataUpdatedAt: state.dataUpdatedAt,
isFetching: state.isFetching,
isRefetching: state.isRefetching,
isSuccess: state.isSuccess,
@@ -1235,7 +1240,7 @@ describe('useQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -1284,7 +1289,7 @@ describe('useQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -1330,7 +1335,7 @@ describe('useQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -1387,7 +1392,7 @@ describe('useQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -1457,7 +1462,7 @@ describe('useQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -1540,7 +1545,7 @@ describe('useQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -1613,7 +1618,7 @@ describe('useQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -1690,7 +1695,7 @@ describe('useQuery', () => {
staleTime: 100,
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
(s) => {
states1.push(s)
},
@@ -1705,7 +1710,7 @@ describe('useQuery', () => {
staleTime: 10,
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
(s) => {
states2.push(s)
},
@@ -1786,7 +1791,7 @@ describe('useQuery', () => {
staleTime: 50,
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -1820,7 +1825,7 @@ describe('useQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -2060,7 +2065,7 @@ describe('useQuery', () => {
return (
-
{state.data ?? 'default'}
+ {state.isPending ? 'default' : state.data}
)
}
@@ -2092,7 +2097,7 @@ describe('useQuery', () => {
refetchOnWindowFocus: false,
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -2128,7 +2133,7 @@ describe('useQuery', () => {
refetchOnWindowFocus: () => false,
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -2164,7 +2169,7 @@ describe('useQuery', () => {
refetchOnWindowFocus: true,
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -2200,7 +2205,7 @@ describe('useQuery', () => {
refetchOnWindowFocus: 'always',
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -2241,7 +2246,7 @@ describe('useQuery', () => {
refetchOnWindowFocus: (query) => (query.state.data || 0) < 1,
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -2303,7 +2308,7 @@ describe('useQuery', () => {
staleTime: Infinity,
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -2350,7 +2355,7 @@ describe('useQuery', () => {
staleTime: 0,
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -2836,14 +2841,14 @@ describe('useQuery', () => {
staleTime: 50,
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
)
return (
-
data: {state.data ?? 'null'}
+
data: {state.data}
isFetching: {state.isFetching}
isStale: {state.isStale}
@@ -2891,7 +2896,7 @@ describe('useQuery', () => {
initialData: 'initial',
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -2933,7 +2938,7 @@ describe('useQuery', () => {
initialData: 'initial',
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -2977,7 +2982,7 @@ describe('useQuery', () => {
initialDataUpdatedAt: oneSecondAgo,
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -3024,7 +3029,7 @@ describe('useQuery', () => {
initialDataUpdatedAt: 0,
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -3067,7 +3072,7 @@ describe('useQuery', () => {
reconcile: false,
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -3340,7 +3345,7 @@ describe('useQuery', () => {
queryFn: () => sleep(10).then(() => 'data'),
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -3387,7 +3392,7 @@ describe('useQuery', () => {
queryFn: () => sleep(10).then(() => 'data'),
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -3462,7 +3467,7 @@ describe('useQuery', () => {
function Page() {
const state = useQuery(() => ({ queryKey: key, queryFn }))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -4121,7 +4126,7 @@ describe('useQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -4204,7 +4209,7 @@ describe('useQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -4335,7 +4340,7 @@ describe('useQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -4457,7 +4462,7 @@ describe('useQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -4512,7 +4517,7 @@ describe('useQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -4622,7 +4627,7 @@ describe('useQuery', () => {
createRenderEffect(
() => state.data,
(data) => {
- if (data) {
+ if (state.isSuccess) {
dataRefs.push(data)
}
},
@@ -4680,9 +4685,7 @@ describe('useQuery', () => {
}))
createTrackedEffect(() => {
- if (state.data) {
- states.push(state.data)
- }
+ states.push(state.data)
})
const forceUpdate = () => {
@@ -4834,7 +4837,7 @@ describe('useQuery', () => {
const state = useQuery(() => ({ queryKey: [key, id()], queryFn }))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -4886,7 +4889,7 @@ describe('useQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -4897,7 +4900,7 @@ describe('useQuery', () => {
queryClient.resetQueries({ queryKey: key })}>
reset
- data: {state.data ?? 'null'}
+ data: {state.data}
isFetching: {state.isFetching}
)
@@ -4966,7 +4969,7 @@ describe('useQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -4980,7 +4983,7 @@ describe('useQuery', () => {
queryClient.resetQueries({ queryKey: key })}>
reset
-
data: {state.data ?? 'null'}
+
data: {state.data}
)
}
@@ -4991,7 +4994,7 @@ describe('useQuery', () => {
))
- expect(rendered.getByText('data: null')).toBeInTheDocument()
+ expect(rendered.getByText('data:')).toBeInTheDocument()
fireEvent.click(rendered.getByRole('button', { name: /refetch/i }))
await vi.advanceTimersByTimeAsync(10)
@@ -4999,7 +5002,7 @@ describe('useQuery', () => {
fireEvent.click(rendered.getByRole('button', { name: /reset/i }))
await vi.advanceTimersByTimeAsync(10)
- expect(rendered.getByText('data: null')).toBeInTheDocument()
+ expect(rendered.getByText('data:')).toBeInTheDocument()
expect(states.length).toBe(4)
@@ -5269,7 +5272,7 @@ describe('useQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
@@ -6146,7 +6149,7 @@ describe('useQuery', () => {
}))
createRenderEffect(
- () => ({ ...state }),
+ () => deep(state),
() => {
states.push(snapshot(state) as any)
},
diff --git a/packages/solid-query/src/types.ts b/packages/solid-query/src/types.ts
index 1c5bd10a24..a1ddfabe8a 100644
--- a/packages/solid-query/src/types.ts
+++ b/packages/solid-query/src/types.ts
@@ -66,10 +66,26 @@ export type UseQueryOptions<
/* --- Create Query and Create Base Query Types --- */
+/**
+ * Reading `data` on a useQuery/useInfiniteQuery result is backed by an async
+ * resource: while the query is loading, the component is suspended into the
+ * nearest `` boundary, so by the time `data` is actually read during
+ * render the value has settled. The type reflects that — `data` is `TData`,
+ * never `undefined`.
+ *
+ * Distributes over the result union so each status variant keeps its other
+ * discriminants (`status`, `error`, ...) and only `data` is narrowed.
+ */
+export type NonNullableData = TResult extends {
+ data: unknown
+}
+ ? Omit & { data: TData }
+ : never
+
export type UseBaseQueryResult<
TData = unknown,
TError = DefaultError,
-> = QueryObserverResult
+> = NonNullableData, TData>
export type UseQueryResult<
TData = unknown,
@@ -132,7 +148,7 @@ export type UseInfiniteQueryOptions<
export type UseInfiniteQueryResult<
TData = unknown,
TError = DefaultError,
-> = InfiniteQueryObserverResult
+> = NonNullableData, TData>
export type DefinedUseInfiniteQueryResult<
TData = unknown,
diff --git a/packages/solid-query/src/useBaseQuery.ts b/packages/solid-query/src/useBaseQuery.ts
index 94e3775f4e..1b8d68b58b 100644
--- a/packages/solid-query/src/useBaseQuery.ts
+++ b/packages/solid-query/src/useBaseQuery.ts
@@ -3,14 +3,17 @@
// why that happens.
import { notifyManager, shouldThrowError } from '@tanstack/query-core'
import {
+ NotReadyError,
createRenderEffect,
createSignal,
createStore,
+ getObserver,
isPending,
onCleanup,
reconcile,
refresh,
runWithOwner,
+ sharedConfig,
snapshot,
untrack,
useContext,
@@ -500,6 +503,39 @@ export function useBaseQuery<
throw state.error
}
+ // `data` is typed non-nullable, so a read that happens before the first
+ // fetch settles has no value to return. Suspend instead: throwing
+ // NotReadyError from a tracking scope sends the reader to the nearest
+ // boundary, mirroring the isServer branch above so both sides
+ // behave the same. The `state` reads here are what re-subscribe the
+ // reader, so it re-runs once the subscriber syncs the settled result.
+ //
+ // Only `isLoading` suspends (pending *and* fetching). A query that is
+ // pending but idle — disabled, or reset with no observer fetching — has
+ // nothing in flight to wait for, so it yields undefined rather than
+ // parking the boundary on a promise that never resolves.
+ //
+ // Untracked reads pass through: event handlers and effect callbacks
+ // peek at the raw value, which keeps imperative access working (and
+ // lets callers observe pending states) without suspending.
+ //
+ // Hydration stands down: while Solid is claiming server-rendered DOM
+ // (`sharedConfig.hydrating`), a throw here bails the claim — the
+ // server rendered this content from settled data that the streaming
+ // hydration channel may not have primed on the client yet — leaving
+ // unclaimed server nodes and crashing the reactive system with
+ // "Potential Infinite Loop Detected". Reads during that window
+ // return the store value, and the per-query hydration coordinator
+ // (see hydrationChannel.ts) re-syncs them once their entry lands.
+ if (
+ prop === 'data' &&
+ getObserver() &&
+ state.isLoading &&
+ !sharedConfig.hydrating
+ ) {
+ throw new NotReadyError(observer.getCurrentQuery())
+ }
+
return Reflect.get(target, prop, receiver)
},
})
diff --git a/packages/solid-query/src/useQueries.ts b/packages/solid-query/src/useQueries.ts
index a17455cbf8..89e1398dfc 100644
--- a/packages/solid-query/src/useQueries.ts
+++ b/packages/solid-query/src/useQueries.ts
@@ -11,7 +11,7 @@ import {
} from 'solid-js'
import { useQueryClient } from './QueryClientProvider'
import { useIsRestoring } from './isRestoring'
-import type { QueryOptions, UseQueryResult } from './types'
+import type { QueryOptions } from './types'
import type { Accessor } from 'solid-js'
import type { QueryClient } from './QueryClient'
import type {
@@ -26,6 +26,14 @@ import type {
ThrowOnError,
} from '@tanstack/query-core'
+// Unlike useQuery, useQueries results are a plain reactive store with no
+// resource backing — reads never suspend, so `data` here stays nullable
+// rather than using the package-wide NonNullableData result type.
+type UseQueryResult<
+ TData = unknown,
+ TError = DefaultError,
+> = QueryObserverResult
+
// This defines the `UseQueryOptions` that are accepted in `QueriesOptions` & `GetOptions`.
// `placeholderData` function does not have a parameter
type UseQueryOptionsForUseQueries<