-
-
Notifications
You must be signed in to change notification settings - Fork 3.3k
feat(query-core, react-query, vue-query): backport v5 some apis to v4 #9140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
v16.19.0 | ||
22.12.0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -141,14 +141,16 @@ export class MutationObserver< | |
? this.currentMutation.state | ||
: getDefaultState<TData, TError, TVariables, TContext>() | ||
|
||
const isLoading = state.status === 'loading' | ||
const result: MutationObserverBaseResult< | ||
TData, | ||
TError, | ||
TVariables, | ||
TContext | ||
> = { | ||
...state, | ||
isLoading: state.status === 'loading', | ||
isLoading, | ||
isPending: isLoading, | ||
Comment on lines
+152
to
+153
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can use isPending like v5 |
||
isSuccess: state.status === 'success', | ||
isError: state.status === 'error', | ||
isIdle: state.status === 'idle', | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,7 @@ import { notifyManager } from './notifyManager' | |
import { Subscribable } from './subscribable' | ||
import type { QueryFilters } from './utils' | ||
import type { Action, QueryState } from './query' | ||
import type { NotifyEvent, QueryKey, QueryOptions } from './types' | ||
import type { NotifyEvent, OmitKeyof, QueryKey, QueryOptions } from './types' | ||
import type { QueryClient } from './queryClient' | ||
import type { QueryObserver } from './queryObserver' | ||
|
||
|
@@ -166,8 +166,21 @@ export class QueryCache extends Subscribable<QueryCacheListener> { | |
} | ||
|
||
find<TQueryFnData = unknown, TError = unknown, TData = TQueryFnData>( | ||
arg1: QueryKey, | ||
arg2?: QueryFilters, | ||
filters: QueryFilters, | ||
): Query<TQueryFnData, TError, TData> | undefined | ||
Comment on lines
168
to
+170
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added a function overload accepting only a |
||
/** | ||
* @deprecated This method should be used with only one object argument. | ||
*/ | ||
find<TQueryFnData = unknown, TError = unknown, TData = TQueryFnData>( | ||
queryKey: QueryKey, | ||
filters?: OmitKeyof<QueryFilters, 'queryKey'>, | ||
): Query<TQueryFnData, TError, TData> | undefined | ||
/** | ||
* @deprecated This method should be used with only one object argument. | ||
*/ | ||
find<TQueryFnData = unknown, TError = unknown, TData = TQueryFnData>( | ||
arg1: QueryKey | QueryFilters, | ||
arg2?: OmitKeyof<QueryFilters, 'queryKey'>, | ||
Comment on lines
+171
to
+183
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tagged jsdoc |
||
): Query<TQueryFnData, TError, TData> | undefined { | ||
const [filters] = parseFilterArgs(arg1, arg2) | ||
|
||
|
@@ -178,10 +191,28 @@ export class QueryCache extends Subscribable<QueryCacheListener> { | |
return this.queries.find((query) => matchQuery(filters, query)) | ||
} | ||
|
||
findAll(queryKey?: QueryKey, filters?: QueryFilters): Query[] | ||
findAll(filters?: QueryFilters): Query[] | ||
findAll(arg1?: QueryKey | QueryFilters, arg2?: QueryFilters): Query[] | ||
findAll(arg1?: QueryKey | QueryFilters, arg2?: QueryFilters): Query[] { | ||
/** | ||
* @deprecated This method should be used with only one object argument. | ||
*/ | ||
findAll( | ||
queryKey?: QueryKey, | ||
filters?: OmitKeyof<QueryFilters, 'queryKey'>, | ||
): Query[] | ||
/** | ||
* @deprecated This method should be used with only one object argument. | ||
*/ | ||
findAll( | ||
arg1?: QueryKey | QueryFilters, | ||
arg2?: OmitKeyof<QueryFilters, 'queryKey'>, | ||
): Query[] | ||
/** | ||
* @deprecated This method should be used with only one object argument. | ||
*/ | ||
findAll( | ||
arg1?: QueryKey | QueryFilters, | ||
arg2?: OmitKeyof<QueryFilters, 'queryKey'>, | ||
): Query[] { | ||
Comment on lines
+195
to
+215
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tagged jsdoc |
||
const [filters] = parseFilterArgs(arg1, arg2) | ||
return Object.keys(filters).length > 0 | ||
? this.queries.filter((query) => matchQuery(filters, query)) | ||
|
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { doNotExecute, queryKey } from './utils' | ||
import type { QueryClient } from '..' | ||
|
||
describe('queryClient', () => { | ||
let queryClient: QueryClient | ||
|
||
it('should be used with queryCache', () => { | ||
doNotExecute(() => { | ||
queryClient.getQueryData(queryKey()) | ||
queryClient.getQueryData(queryKey(), {}) | ||
}) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,20 @@ import type { QueryCache } from './queryCache' | |
import type { MutationCache } from './mutationCache' | ||
import type { Logger } from './logger' | ||
|
||
export type NonUndefinedGuard<T> = T extends undefined ? never : T | ||
|
||
export type OmitKeyof< | ||
TObject, | ||
TKey extends TStrictly extends 'safely' | ||
? | ||
| keyof TObject | ||
| (string & Record<never, never>) | ||
| (number & Record<never, never>) | ||
| (symbol & Record<never, never>) | ||
: keyof TObject, | ||
TStrictly extends 'strictly' | 'safely' = 'strictly', | ||
> = Omit<TObject, TKey> | ||
|
||
export type QueryKey = readonly unknown[] | ||
|
||
export type QueryFunction< | ||
|
@@ -256,6 +270,8 @@ export interface QueryObserverOptions< | |
/** | ||
* Set this to `true` to keep the previous `data` when fetching based on a new query key. | ||
* Defaults to `false`. | ||
* | ||
* @deprecated keepPreviousData will be removed in the next major version. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tagged |
||
*/ | ||
keepPreviousData?: boolean | ||
/** | ||
|
@@ -406,6 +422,9 @@ export interface QueryObserverBaseResult<TData = unknown, TError = unknown> { | |
refetch: <TPageData>( | ||
options?: RefetchOptions & RefetchQueryFilters<TPageData>, | ||
) => Promise<QueryObserverResult<TData, TError>> | ||
/** | ||
* @deprecated This method will be removed in the next major version. Use `QueryClient.removeQueries` instead. | ||
*/ | ||
remove: () => void | ||
status: QueryStatus | ||
fetchStatus: FetchStatus | ||
|
@@ -646,6 +665,7 @@ export interface MutationObserverBaseResult< | |
isError: boolean | ||
isIdle: boolean | ||
isLoading: boolean | ||
isPending: boolean | ||
isSuccess: boolean | ||
mutate: MutateFunction<TData, TError, TVariables, TContext> | ||
reset: () => void | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,270 @@ | ||
import { expectTypeOf } from 'expect-type' | ||
import { | ||
QueryCache, | ||
type UseQueryResult, | ||
useQueries, | ||
useQuery, | ||
useQueryClient, | ||
} from '@tanstack/react-query' | ||
import { queryOptions } from '..' | ||
import { useSuspenseQueries, useSuspenseQuery } from '..' | ||
import { type UseSuspenseQueryResult } from '../useSuspenseQuery' | ||
import { doNotExecute } from './utils' | ||
import type { DefinedUseQueryResult } from '@tanstack/react-query' | ||
|
||
const queryKey = ['key'] as const | ||
const queryFn = () => Promise.resolve({ field: 'success' }) | ||
|
||
describe('queryOptions', () => { | ||
it('should be used with useQuery', () => { | ||
doNotExecute(() => { | ||
const dd = useQuery( | ||
queryOptions({ | ||
queryKey, | ||
queryFn, | ||
}), | ||
) | ||
expectTypeOf(dd).toEqualTypeOf<UseQueryResult<{ field: string }>>() | ||
expectTypeOf( | ||
useQuery({ | ||
...queryOptions({ | ||
queryKey, | ||
queryFn, | ||
}), | ||
select: (data) => data.field, | ||
}), | ||
).toEqualTypeOf<UseQueryResult<string>>() | ||
expectTypeOf( | ||
useQuery({ | ||
...queryOptions({ | ||
queryKey, | ||
queryFn, | ||
select: (data) => data.field, | ||
}), | ||
}), | ||
).toEqualTypeOf<UseQueryResult<string>>() | ||
expectTypeOf( | ||
useQuery({ | ||
...queryOptions({ | ||
queryKey, | ||
queryFn, | ||
}), | ||
initialData: { field: 'success' }, | ||
}), | ||
).toEqualTypeOf<DefinedUseQueryResult<{ field: string }>>() | ||
expectTypeOf( | ||
useQuery({ | ||
...queryOptions({ | ||
queryKey, | ||
queryFn, | ||
}), | ||
initialData: { field: 'success' }, | ||
select: (data) => data.field, | ||
}), | ||
).toEqualTypeOf<DefinedUseQueryResult<string>>() | ||
expectTypeOf( | ||
useQuery({ | ||
...queryOptions({ | ||
queryKey, | ||
queryFn, | ||
}), | ||
initialData: undefined, | ||
select: (data) => data.field, | ||
}), | ||
).toEqualTypeOf<UseQueryResult<string>>() | ||
expectTypeOf( | ||
useQuery({ | ||
...queryOptions({ | ||
queryKey, | ||
queryFn, | ||
}), | ||
initialData: () => undefined, | ||
select: (data) => data.field, | ||
}), | ||
).toEqualTypeOf<UseQueryResult<string>>() | ||
expectTypeOf( | ||
useQuery({ | ||
...queryOptions({ | ||
queryKey, | ||
queryFn, | ||
select: (data) => data.field, | ||
}), | ||
refetchInterval: 1000, | ||
}), | ||
).toEqualTypeOf<UseQueryResult<string>>() | ||
}) | ||
}) | ||
it('should be used with useSuspenseQuery', () => { | ||
doNotExecute(() => { | ||
expectTypeOf( | ||
useSuspenseQuery( | ||
queryOptions({ | ||
queryKey, | ||
queryFn, | ||
}), | ||
), | ||
).toEqualTypeOf<UseSuspenseQueryResult<{ field: string }>>() | ||
|
||
expectTypeOf( | ||
useSuspenseQuery({ | ||
...queryOptions({ | ||
queryKey, | ||
queryFn, | ||
}), | ||
select: (data) => data.field, | ||
}), | ||
).toEqualTypeOf<UseSuspenseQueryResult<string>>() | ||
expectTypeOf( | ||
useSuspenseQuery({ | ||
...queryOptions({ | ||
queryKey, | ||
queryFn, | ||
}), | ||
initialData: { field: 'success' }, | ||
}), | ||
).toEqualTypeOf<UseSuspenseQueryResult<{ field: string }>>() | ||
expectTypeOf( | ||
useSuspenseQuery({ | ||
...queryOptions({ | ||
queryKey, | ||
queryFn, | ||
}), | ||
initialData: undefined, | ||
select: (data) => data.field, | ||
}), | ||
).toEqualTypeOf<UseSuspenseQueryResult<string>>() | ||
expectTypeOf( | ||
useSuspenseQuery({ | ||
...queryOptions({ | ||
queryKey, | ||
queryFn, | ||
}), | ||
initialData: { field: 'success' }, | ||
select: (data) => data.field, | ||
}), | ||
).toEqualTypeOf<UseSuspenseQueryResult<string>>() | ||
expectTypeOf( | ||
useSuspenseQuery({ | ||
...queryOptions({ | ||
queryKey, | ||
queryFn, | ||
select: (data) => data.field, | ||
}), | ||
refetchInterval: 1000, | ||
}), | ||
).toEqualTypeOf<UseSuspenseQueryResult<string>>() | ||
}) | ||
}) | ||
it('should be used with useQueries', () => { | ||
doNotExecute(() => { | ||
const [query1, query2, query3, query4] = useQueries({ | ||
queries: [ | ||
queryOptions({ | ||
queryKey, | ||
queryFn, | ||
}), | ||
queryOptions({ | ||
queryKey, | ||
queryFn, | ||
initialData: { field: 'success' }, | ||
}), | ||
{ | ||
...queryOptions({ | ||
queryKey, | ||
queryFn, | ||
}), | ||
initialData: { field: 'success' }, | ||
}, | ||
{ | ||
...queryOptions({ | ||
queryKey, | ||
queryFn, | ||
}), | ||
initialData: undefined, | ||
}, | ||
], | ||
}) | ||
expectTypeOf(query1).toEqualTypeOf<UseQueryResult<{ field: string }>>() | ||
expectTypeOf(query2).toEqualTypeOf< | ||
DefinedUseQueryResult<{ field: string }> | ||
>() | ||
expectTypeOf(query3).toEqualTypeOf< | ||
DefinedUseQueryResult<{ field: string }> | ||
>() | ||
Comment on lines
+188
to
+193
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed the issue where |
||
expectTypeOf(query4).toEqualTypeOf<UseQueryResult<{ field: string }>>() | ||
}) | ||
}) | ||
it('should be used with useSuspenseQueries', () => { | ||
doNotExecute(() => { | ||
const [query1, query2, query3, query4, query5] = useSuspenseQueries({ | ||
queries: [ | ||
queryOptions({ | ||
queryKey, | ||
queryFn, | ||
}), | ||
queryOptions({ | ||
queryKey, | ||
queryFn, | ||
initialData: { field: 'success' }, | ||
}), | ||
{ | ||
...queryOptions({ | ||
queryKey, | ||
queryFn, | ||
}), | ||
initialData: { field: 'success' }, | ||
}, | ||
{ | ||
...queryOptions({ | ||
queryKey, | ||
queryFn, | ||
}), | ||
initialData: undefined, | ||
}, | ||
{ | ||
...queryOptions({ | ||
queryKey, | ||
queryFn, | ||
select: (data) => data.field, | ||
}), | ||
}, | ||
], | ||
}) | ||
expectTypeOf(query1).toEqualTypeOf< | ||
UseSuspenseQueryResult<{ field: string }> | ||
>() | ||
expectTypeOf(query2).toEqualTypeOf< | ||
UseSuspenseQueryResult<{ field: string }> | ||
>() | ||
expectTypeOf(query3).toEqualTypeOf< | ||
UseSuspenseQueryResult<{ field: string }> | ||
>() | ||
expectTypeOf(query4).toEqualTypeOf< | ||
UseSuspenseQueryResult<{ field: string }> | ||
>() | ||
expectTypeOf(query5).toEqualTypeOf<UseSuspenseQueryResult<string>>() | ||
}) | ||
}) | ||
it('should be used with useQueryClient', () => { | ||
doNotExecute(async () => { | ||
const queryClient = useQueryClient() | ||
queryClient.invalidateQueries(queryOptions({ queryKey, queryFn })) | ||
queryClient.resetQueries(queryOptions({ queryKey, queryFn })) | ||
queryClient.removeQueries(queryOptions({ queryKey, queryFn })) | ||
queryClient.cancelQueries(queryOptions({ queryKey, queryFn })) | ||
queryClient.prefetchQuery(queryOptions({ queryKey, queryFn })) | ||
queryClient.refetchQueries(queryOptions({ queryKey, queryFn })) | ||
expectTypeOf( | ||
await queryClient.fetchQuery(queryOptions({ queryKey, queryFn })), | ||
).toEqualTypeOf<{ field: string }>() | ||
}) | ||
}) | ||
it('should be used with queryCache', () => { | ||
doNotExecute(() => { | ||
const queryCache = new QueryCache() | ||
queryCache.find({ queryKey: [] }) | ||
queryCache.find(queryOptions({ queryKey, queryFn })) | ||
queryCache.find(queryKey) | ||
}) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import { expectTypeOf } from 'expect-type' | ||
import { queryOptions } from '..' | ||
import { useSuspenseQueries } from '..' | ||
import { doNotExecute } from './utils' | ||
import type { UseSuspenseQueryResult } from '../useSuspenseQuery' | ||
|
||
export const queryKey = ['key'] as const | ||
const sleep = (ms: number) => | ||
new Promise<undefined>((resolve) => setTimeout(() => resolve(undefined), ms)) | ||
export const queryFn = () => sleep(10).then(() => ({ text: 'response' })) | ||
export const select = (data: Awaited<ReturnType<typeof queryFn>>) => data.text | ||
|
||
describe('useSuspenseQueries', () => { | ||
it('type check', () => { | ||
doNotExecute(() => { | ||
useSuspenseQueries({ | ||
queries: [ | ||
{ | ||
queryKey: [...queryKey, 1] as const, | ||
queryFn, | ||
// @ts-expect-error no suspense | ||
suspense: false, | ||
}, | ||
] as const, | ||
}) | ||
useSuspenseQueries({ | ||
queries: [ | ||
{ | ||
queryKey: [...queryKey, 2] as const, | ||
queryFn, | ||
select, | ||
// @ts-expect-error no suspense | ||
suspense: true, | ||
}, | ||
] as const, | ||
}) | ||
useSuspenseQueries({ | ||
queries: [ | ||
{ | ||
queryKey: [...queryKey, 3] as const, | ||
queryFn, | ||
// @ts-expect-error no enabled | ||
enabled: true, | ||
}, | ||
] as const, | ||
}) | ||
useSuspenseQueries({ | ||
queries: [ | ||
{ | ||
queryKey: [...queryKey, 4] as const, | ||
queryFn, | ||
// @ts-expect-error no enabled | ||
enabled: true, | ||
select, | ||
}, | ||
] as const, | ||
}) | ||
useSuspenseQueries({ | ||
queries: [ | ||
{ | ||
queryKey: [...queryKey, 4] as const, | ||
queryFn, | ||
// @ts-expect-error no networkMode | ||
networkMode: 'always', | ||
select, | ||
}, | ||
] as const, | ||
}) | ||
useSuspenseQueries({ | ||
queries: [ | ||
queryOptions({ | ||
queryKey: [...queryKey, 4] as const, | ||
queryFn: () => Promise.resolve({ field: 'success' }), | ||
select: (data) => data.field, | ||
}), | ||
] as const, | ||
}) | ||
// @ts-expect-error if no items | ||
useSuspenseQueries({}) | ||
// @ts-expect-error if no items | ||
useSuspenseQueries() | ||
|
||
const [query1, query2, query3] = useSuspenseQueries({ | ||
queries: [ | ||
{ queryKey: [...queryKey, 5] as const, queryFn }, | ||
{ queryKey: [...queryKey, 6] as const, queryFn, select }, | ||
queryOptions({ | ||
queryKey: [...queryKey, 4] as const, | ||
queryFn: () => Promise.resolve({ field: 'success' }), | ||
select: (data) => data.field, | ||
}), | ||
] as const, | ||
}) | ||
|
||
expectTypeOf(query1).toEqualTypeOf< | ||
UseSuspenseQueryResult<{ text: string }> | ||
>() | ||
expectTypeOf(query2).toEqualTypeOf<UseSuspenseQueryResult<string>>() | ||
expectTypeOf(query3).toEqualTypeOf<UseSuspenseQueryResult<string>>() | ||
}) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import { expectTypeOf } from 'expect-type' | ||
import { | ||
type UseSuspenseQueryResult, | ||
useSuspenseQuery, | ||
} from '../useSuspenseQuery' | ||
import { queryOptions } from '..' | ||
import { doNotExecute } from './utils' | ||
|
||
const queryKey = ['key'] as const | ||
const sleep = (ms: number) => | ||
new Promise<undefined>((resolve) => setTimeout(() => resolve(undefined), ms)) | ||
const queryFn = () => sleep(10).then(() => ({ text: 'response' })) | ||
|
||
describe('useSuspenseQuery', () => { | ||
it('type check', () => { | ||
doNotExecute(() => { | ||
//@ts-expect-error no arg | ||
useSuspenseQuery() | ||
useSuspenseQuery({ | ||
queryKey, | ||
queryFn, | ||
//@ts-expect-error no suspense | ||
suspense: boolean, | ||
}) | ||
useSuspenseQuery({ | ||
queryKey, | ||
queryFn, | ||
//@ts-expect-error no useErrorBoundary | ||
useErrorBoundary: boolean, | ||
}) | ||
useSuspenseQuery({ | ||
queryKey, | ||
queryFn, | ||
//@ts-expect-error no enabled | ||
enabled: boolean, | ||
}) | ||
useSuspenseQuery({ | ||
queryKey, | ||
queryFn, | ||
//@ts-expect-error no placeholderData | ||
placeholderData: 'placeholder', | ||
}) | ||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions | ||
useSuspenseQuery({ | ||
queryKey, | ||
queryFn, | ||
//@ts-expect-error no isPlaceholderData | ||
}).isPlaceholderData | ||
useSuspenseQuery({ | ||
queryKey, | ||
queryFn, | ||
//@ts-expect-error no networkMode | ||
networkMode: 'always', | ||
}) | ||
|
||
expectTypeOf(useSuspenseQuery({ queryKey, queryFn })).toEqualTypeOf< | ||
UseSuspenseQueryResult<{ text: string }> | ||
>() | ||
expectTypeOf( | ||
useSuspenseQuery({ | ||
queryKey, | ||
queryFn, | ||
select: (data) => data.text, | ||
}), | ||
).toEqualTypeOf<UseSuspenseQueryResult<string>>() | ||
const options = queryOptions({ | ||
queryKey, | ||
queryFn, | ||
}) | ||
expectTypeOf(useSuspenseQuery(options)).toEqualTypeOf< | ||
UseSuspenseQueryResult<{ text: string }> | ||
>() | ||
expectTypeOf( | ||
useSuspenseQuery({ | ||
...options, | ||
select: (data) => data.text, | ||
}), | ||
).toEqualTypeOf<UseSuspenseQueryResult<string>>() | ||
}) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import type { | ||
InitialDataFunction, | ||
NonUndefinedGuard, | ||
OmitKeyof, | ||
QueryKey, | ||
WithRequired, | ||
} from '@tanstack/query-core' | ||
import type { UseQueryOptions } from './types' | ||
|
||
type UseQueryOptionsOmitted< | ||
TQueryFnData = unknown, | ||
TError = unknown, | ||
TData = TQueryFnData, | ||
TQueryKey extends QueryKey = QueryKey, | ||
> = OmitKeyof< | ||
UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>, | ||
'onSuccess' | 'onError' | 'onSettled' | 'refetchInterval' | ||
> | ||
|
||
type ProhibitedQueryOptionsKeyInV5 = keyof Pick< | ||
UseQueryOptionsOmitted, | ||
'useErrorBoundary' | 'suspense' | 'getNextPageParam' | 'getPreviousPageParam' | ||
> | ||
|
||
export type UndefinedInitialDataOptions< | ||
TQueryFnData = unknown, | ||
TError = unknown, | ||
TData = TQueryFnData, | ||
TQueryKey extends QueryKey = QueryKey, | ||
> = UseQueryOptionsOmitted<TQueryFnData, TError, TData, TQueryKey> & { | ||
initialData?: | ||
| undefined | ||
| InitialDataFunction<NonUndefinedGuard<TQueryFnData>> | ||
| NonUndefinedGuard<TQueryFnData> | ||
} | ||
|
||
export type DefinedInitialDataOptions< | ||
TQueryFnData = unknown, | ||
TError = unknown, | ||
TData = TQueryFnData, | ||
TQueryKey extends QueryKey = QueryKey, | ||
> = UseQueryOptionsOmitted<TQueryFnData, TError, TData, TQueryKey> & { | ||
initialData: | ||
| NonUndefinedGuard<TQueryFnData> | ||
| (() => NonUndefinedGuard<TQueryFnData>) | ||
} | ||
|
||
export function queryOptions< | ||
TQueryFnData = unknown, | ||
TError = unknown, | ||
TData = TQueryFnData, | ||
TQueryKey extends QueryKey = QueryKey, | ||
>( | ||
options: WithRequired< | ||
OmitKeyof< | ||
DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>, | ||
ProhibitedQueryOptionsKeyInV5 | ||
>, | ||
'queryKey' | ||
>, | ||
): WithRequired< | ||
DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>, | ||
'queryKey' | ||
> | ||
|
||
export function queryOptions< | ||
TQueryFnData = unknown, | ||
TError = unknown, | ||
TData = TQueryFnData, | ||
TQueryKey extends QueryKey = QueryKey, | ||
>( | ||
options: WithRequired< | ||
OmitKeyof< | ||
UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>, | ||
ProhibitedQueryOptionsKeyInV5 | ||
>, | ||
'queryKey' | ||
>, | ||
): WithRequired< | ||
UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>, | ||
'queryKey' | ||
> | ||
|
||
export function queryOptions(options: unknown) { | ||
return options | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
import { useQueries } from './useQueries' | ||
import type { UseQueryOptions } from './types' | ||
import type { NetworkMode, QueryFunction } from '@tanstack/query-core' | ||
import type { | ||
UseSuspenseQueryOptions, | ||
UseSuspenseQueryResult, | ||
} from './useSuspenseQuery' | ||
|
||
// Avoid TS depth-limit error in case of large array literal | ||
type MAXIMUM_DEPTH = 20 | ||
|
||
type GetSuspenseOptions<T> = | ||
// Part 1: responsible for applying explicit type parameter to function arguments, if object { queryFnData: TQueryFnData, error: TError, data: TData } | ||
T extends { | ||
queryFnData: infer TQueryFnData | ||
error?: infer TError | ||
data: infer TData | ||
} | ||
? UseSuspenseQueryOptions<TQueryFnData, TError, TData> | ||
: T extends { queryFnData: infer TQueryFnData; error?: infer TError } | ||
? UseSuspenseQueryOptions<TQueryFnData, TError> | ||
: T extends { data: infer TData; error?: infer TError } | ||
? UseSuspenseQueryOptions<unknown, TError, TData> | ||
: // Part 2: responsible for applying explicit type parameter to function arguments, if tuple [TQueryFnData, TError, TData] | ||
T extends [infer TQueryFnData, infer TError, infer TData] | ||
? UseSuspenseQueryOptions<TQueryFnData, TError, TData> | ||
: T extends [infer TQueryFnData, infer TError] | ||
? UseSuspenseQueryOptions<TQueryFnData, TError> | ||
: T extends [infer TQueryFnData] | ||
? UseSuspenseQueryOptions<TQueryFnData> | ||
: // Part 3: responsible for inferring and enforcing type if no explicit parameter was provided | ||
T extends { | ||
queryFn?: QueryFunction<infer TQueryFnData, infer TQueryKey> | ||
select?: (data: any) => infer TData | ||
} | ||
? UseSuspenseQueryOptions<TQueryFnData, unknown, TData, TQueryKey> | ||
: T extends { | ||
queryFn?: QueryFunction<infer TQueryFnData, infer TQueryKey> | ||
} | ||
? UseSuspenseQueryOptions<TQueryFnData, unknown, TQueryFnData, TQueryKey> | ||
: // Fallback | ||
UseSuspenseQueryOptions | ||
|
||
type GetSuspenseResults<T> = | ||
// Part 1: responsible for mapping explicit type parameter to function result, if object | ||
T extends { queryFnData: any; error?: infer TError; data: infer TData } | ||
? UseSuspenseQueryResult<TData, TError> | ||
: T extends { queryFnData: infer TQueryFnData; error?: infer TError } | ||
? UseSuspenseQueryResult<TQueryFnData, TError> | ||
: T extends { data: infer TData; error?: infer TError } | ||
? UseSuspenseQueryResult<TData, TError> | ||
: // Part 2: responsible for mapping explicit type parameter to function result, if tuple | ||
T extends [any, infer TError, infer TData] | ||
? UseSuspenseQueryResult<TData, TError> | ||
: T extends [infer TQueryFnData, infer TError] | ||
? UseSuspenseQueryResult<TQueryFnData, TError> | ||
: T extends [infer TQueryFnData] | ||
? UseSuspenseQueryResult<TQueryFnData> | ||
: // Part 3: responsible for mapping inferred type to results, if no explicit parameter was provided | ||
T extends { | ||
queryFn?: QueryFunction<infer TQueryFnData, any> | ||
select?: (data: any) => infer TData | ||
} | ||
? UseSuspenseQueryResult<unknown extends TData ? TQueryFnData : TData> | ||
: T extends { | ||
queryFn?: QueryFunction<infer TQueryFnData, any> | ||
} | ||
? UseSuspenseQueryResult<TQueryFnData> | ||
: // Fallback | ||
UseSuspenseQueryResult | ||
|
||
/** | ||
* SuspenseQueriesOptions reducer recursively unwraps function arguments to infer/enforce type param | ||
*/ | ||
export type SuspenseQueriesOptions< | ||
T extends Array<any>, | ||
TResult extends Array<any> = [], | ||
TDepth extends ReadonlyArray<number> = [], | ||
> = TDepth['length'] extends MAXIMUM_DEPTH | ||
? Array<UseSuspenseQueryOptions> | ||
: T extends [] | ||
? [] | ||
: T extends [infer Head] | ||
? [...TResult, GetSuspenseOptions<Head>] | ||
: T extends [infer Head, ...infer Tail] | ||
? SuspenseQueriesOptions< | ||
[...Tail], | ||
[...TResult, GetSuspenseOptions<Head>], | ||
[...TDepth, 1] | ||
> | ||
: Array<unknown> extends T | ||
? T | ||
: // If T is *some* array but we couldn't assign unknown[] to it, then it must hold some known/homogenous type! | ||
// use this to infer the param types in the case of Array.map() argument | ||
T extends Array< | ||
UseSuspenseQueryOptions< | ||
infer TQueryFnData, | ||
infer TError, | ||
infer TData, | ||
infer TQueryKey | ||
> | ||
> | ||
? Array<UseSuspenseQueryOptions<TQueryFnData, TError, TData, TQueryKey>> | ||
: // Fallback | ||
Array<UseSuspenseQueryOptions> | ||
|
||
/** | ||
* SuspenseQueriesResults reducer recursively maps type param to results | ||
*/ | ||
export type SuspenseQueriesResults< | ||
T extends Array<any>, | ||
TResult extends Array<any> = [], | ||
TDepth extends ReadonlyArray<number> = [], | ||
> = TDepth['length'] extends MAXIMUM_DEPTH | ||
? Array<UseSuspenseQueryResult> | ||
: T extends [] | ||
? [] | ||
: T extends [infer Head] | ||
? [...TResult, GetSuspenseResults<Head>] | ||
: T extends [infer Head, ...infer Tail] | ||
? SuspenseQueriesResults< | ||
[...Tail], | ||
[...TResult, GetSuspenseResults<Head>], | ||
[...TDepth, 1] | ||
> | ||
: T extends Array< | ||
UseSuspenseQueryOptions< | ||
infer TQueryFnData, | ||
infer TError, | ||
infer TData, | ||
any | ||
> | ||
> | ||
? // Dynamic-size (homogenous) UseQueryOptions array: map directly to array of results | ||
Array< | ||
UseSuspenseQueryResult< | ||
unknown extends TData ? TQueryFnData : TData, | ||
TError | ||
> | ||
> | ||
: // Fallback | ||
Array<UseSuspenseQueryResult> | ||
|
||
export function useSuspenseQueries<T extends any[]>({ | ||
queries, | ||
context, | ||
}: { | ||
queries: readonly [...SuspenseQueriesOptions<T>] | ||
context?: UseQueryOptions['context'] | ||
}): SuspenseQueriesResults<T> { | ||
return useQueries({ | ||
queries: queries.map((query) => ({ | ||
...query, | ||
enabled: true, | ||
useErrorBoundary: true, | ||
suspense: true, | ||
placeholderData: undefined, | ||
networkMode: 'always' as NetworkMode, | ||
})), | ||
context, | ||
}) as SuspenseQueriesResults<T> | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { QueryObserver } from '@tanstack/query-core' | ||
import { useBaseQuery } from './useBaseQuery' | ||
import type { | ||
DefinedQueryObserverResult, | ||
OmitKeyof, | ||
QueryKey, | ||
} from '@tanstack/query-core' | ||
import type { UseQueryOptions } from './types' | ||
|
||
type DistributiveOmit<TObject, TKey extends keyof TObject> = TObject extends any | ||
? Omit<TObject, TKey> | ||
: never | ||
|
||
export type UseSuspenseQueryResult< | ||
TData = unknown, | ||
TError = unknown, | ||
> = DistributiveOmit< | ||
DefinedQueryObserverResult<TData, TError>, | ||
'isPlaceholderData' | ||
> | ||
|
||
export type UseSuspenseQueryOptions< | ||
TQueryFnData = unknown, | ||
TError = unknown, | ||
TData = TQueryFnData, | ||
TQueryKey extends QueryKey = QueryKey, | ||
> = OmitKeyof< | ||
UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>, | ||
| 'enabled' | ||
| 'useErrorBoundary' | ||
| 'suspense' | ||
| 'placeholderData' | ||
| 'networkMode' | ||
| 'onSuccess' | ||
| 'onError' | ||
| 'onSettled' | ||
| 'getPreviousPageParam' | ||
| 'getNextPageParam' | ||
> | ||
|
||
export function useSuspenseQuery< | ||
TQueryFnData = unknown, | ||
TError = unknown, | ||
TData = TQueryFnData, | ||
TQueryKey extends QueryKey = QueryKey, | ||
>(options: UseSuspenseQueryOptions<TQueryFnData, TError, TData, TQueryKey>) { | ||
return useBaseQuery( | ||
{ | ||
...options, | ||
enabled: true, | ||
useErrorBoundary: true, | ||
suspense: true, | ||
placeholderData: undefined, | ||
networkMode: 'always', | ||
}, | ||
QueryObserver, | ||
) as UseSuspenseQueryResult<TData, TError> | ||
} |
Large diffs are not rendered by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm using
expect-type
(used by vitest internally) here to ensure type safety in our type tests for useSuspenseQuery, useSuspenseQueries, queryOptions.