From 2ea0ccd01f1e861186b93cefa920e47d85db8476 Mon Sep 17 00:00:00 2001 From: Igor Oleinikov Date: Mon, 30 Mar 2020 15:23:50 -0700 Subject: [PATCH] [react-query] Update API to 1.10.0 (#43438) * [react-query] Update API to 1.10.0 - breaking changes * Make use of extra variables * Fixing lint errors * Complete useInfiniteQuery * Add global config * Add queryCache * Allow keys and variables with infinite tails * Add discriminated unions for base and paginated queries * Discriminated union for mutation result * Require first key element to be a string subtype --- types/react-query/index.d.ts | 474 ++++++++++++++++++++----- types/react-query/package.json | 6 + types/react-query/react-query-tests.ts | 367 +++++++++++++++---- 3 files changed, 682 insertions(+), 165 deletions(-) create mode 100644 types/react-query/package.json diff --git a/types/react-query/index.d.ts b/types/react-query/index.d.ts index 045ac78256..ba52096436 100644 --- a/types/react-query/index.d.ts +++ b/types/react-query/index.d.ts @@ -1,32 +1,171 @@ -// Type definitions for react-query 0.3 +// Type definitions for react-query 1.1 // Project: https://github.com/tannerlinsley/react-query // Definitions by: Lukasz Fiszer // Jace Hensley // Matteo Frana +// Igor Oleinikov // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// Minimum TypeScript Version: 3.7 -import { ComponentType } from 'react'; +import * as React from 'react'; +import * as _ from 'ts-toolbelt'; -// overloaded useQuery function with pagination -export function useQuery( - queryKey: QueryKey, - queryFn: QueryFunction, - options: QueryOptionsPaginated -): QueryResultPaginated; +// overloaded useQuery function +export function useQuery( + queryKey: TKey | false | null | undefined | (() => TKey | false | null | undefined), + queryFn: QueryFunction, + config?: QueryOptions, +): QueryResult; -export function useQuery( - queryKey: QueryKey, - queryFn: QueryFunction, - options?: QueryOptions -): QueryResult; +export function useQuery( + queryKey: TKey | false | null | undefined | (() => TKey | false | null | undefined), + queryFn: QueryFunction, + config?: QueryOptions, +): QueryResult; -export type QueryKey = string | [string, TVariables] | false | null | QueryKeyFunction; -export type QueryKeyFunction = () => string | [string, TVariables] | false | null; +export function useQuery( + queryKey: TKey | false | null | undefined | (() => TKey | false | null | undefined), + variables: TVariables, + queryFn: QueryFunctionWithVariables, + config?: QueryOptions, +): QueryResult; -export type QueryFunction = (variables: TVariables) => Promise; +export function useQuery( + queryKey: TKey | false | null | undefined | (() => TKey | false | null | undefined), + variables: TVariables, + queryFn: QueryFunctionWithVariables, + config?: QueryOptions, +): QueryResult; -export interface QueryOptions { +export function useQuery({ + queryKey, + variables, + queryFn, + config, +}: { + queryKey: TKey | false | null | undefined | (() => TKey | false | null | undefined); + variables?: TVariables; + queryFn: QueryFunctionWithVariables; + config?: QueryOptions; +}): QueryResult; + +// usePaginatedQuery +export function usePaginatedQuery( + queryKey: TKey | false | null | undefined | (() => TKey | false | null | undefined), + queryFn: QueryFunction, + config?: QueryOptions, +): PaginatedQueryResult; + +export function usePaginatedQuery( + queryKey: TKey | false | null | undefined | (() => TKey | false | null | undefined), + queryFn: QueryFunction, + config?: QueryOptions, +): PaginatedQueryResult; + +export function usePaginatedQuery( + queryKey: TKey | false | null | undefined | (() => TKey | false | null | undefined), + variables: TVariables, + queryFn: QueryFunctionWithVariables, + config?: QueryOptions, +): PaginatedQueryResult; + +export function usePaginatedQuery( + queryKey: TKey | false | null | undefined | (() => TKey | false | null | undefined), + variables: TVariables, + queryFn: QueryFunctionWithVariables, + config?: QueryOptions, +): PaginatedQueryResult; + +export function usePaginatedQuery({ + queryKey, + variables, + queryFn, + config, +}: { + queryKey: TKey | false | null | undefined | (() => TKey | false | null | undefined); + variables?: TVariables; + queryFn: QueryFunctionWithVariables; + config?: QueryOptions; +}): PaginatedQueryResult; + +// useInfiniteQuery +export function useInfiniteQuery( + queryKey: TKey | false | null | undefined | (() => TKey | false | null | undefined), + queryFn: InfiniteQueryFunction, + config?: InfiniteQueryOptions, +): InfiniteQueryResult; + +export function useInfiniteQuery( + queryKey: TKey | false | null | undefined | (() => TKey | false | null | undefined), + queryFn: InfiniteQueryFunction, + config?: InfiniteQueryOptions, +): InfiniteQueryResult; + +export function useInfiniteQuery( + queryKey: TKey | false | null | undefined | (() => TKey | false | null | undefined), + variables: TVariables, + queryFn: InfiniteQueryFunctionWithVariables, + config?: InfiniteQueryOptions, +): InfiniteQueryResult; + +export function useInfiniteQuery( + queryKey: TKey | false | null | undefined | (() => TKey | false | null | undefined), + variables: TVariables, + queryFn: InfiniteQueryFunctionWithVariables, + config?: InfiniteQueryOptions, +): InfiniteQueryResult; + +export function useInfiniteQuery< + TResult, + TKey extends AnyQueryKey, + TMoreVariable, + TVariables extends AnyVariables = [] +>({ + queryKey, + variables, + queryFn, + config, +}: { + queryKey: TKey | false | null | undefined | (() => TKey | false | null | undefined); + variables?: TVariables; + queryFn: InfiniteQueryFunctionWithVariables; + config?: InfiniteQueryOptions; +}): InfiniteQueryResult; + +export type QueryKeyPart = string | object | boolean | number | null | readonly QueryKeyPart[] | null | undefined; +export type AnyQueryKey = readonly [string, ...QueryKeyPart[]]; // this forces the key to be inferred as a tuple +export type AnyVariables = readonly [] | readonly [any, ...any[]]; // this forces the variables to be inferred as a tuple + +export type QueryFunction = (...key: TKey) => Promise; +export type QueryFunctionWithVariables = ( + ...key: _.List.Concat +) => Promise; + +export type InfiniteQueryFunction = ( + ...keysAndMore: _.List.Append | TKey +) => Promise; +export type InfiniteQueryFunctionWithVariables< + TResult, + TKey extends AnyQueryKey, + TVariables extends AnyVariables, + TMoreVariable +> = ( + ...keysAndVariablesAndMore: + | _.List.Append<_.List.Concat, TMoreVariable> + | _.List.Concat +) => Promise; + +export interface BaseQueryOptions { + /** + * Set this to `true` to disable automatic refetching when the query mounts or changes query keys. + * To refetch the query, use the `refetch` method returned from the `useQuery` instance. + */ manual?: boolean; + /** + * If `false`, failed queries will not retry by default. + * If `true`, failed queries will retry infinitely. + * If set to an integer number, e.g. 3, failed queries will retry until the failed query count meets that number. + */ retry?: boolean | number; retryDelay?: (retryAttempt: number) => number; staleTime?: number; @@ -34,109 +173,248 @@ export interface QueryOptions { refetchInterval?: false | number; refetchIntervalInBackground?: boolean; refetchOnWindowFocus?: boolean; - onError?: (err: any) => void; - onSuccess?: (data: TResult) => void; + refetchOnMount?: boolean; + onError?: (err: unknown) => void; suspense?: boolean; +} + +export interface QueryOptions extends BaseQueryOptions { + onSuccess?: (data: TResult) => void; + onSettled?: (data: TResult | undefined, error: unknown | null) => void; initialData?: TResult; } -export interface QueryOptionsPaginated extends QueryOptions { - paginated: true; - getCanFetchMore: (lastPage: TResult, allPages: TResult[]) => boolean; +export interface InfiniteQueryOptions extends QueryOptions { + getFetchMore: (lastPage: TResult, allPages: TResult[]) => TMoreVariable | false; } -export interface QueryResult { - data: null | TResult; - error: null | Error; - isLoading: boolean; +export interface QueryResultBase { + status: 'loading' | 'error' | 'success'; + error: null | unknown; isFetching: boolean; - isCached: boolean; failureCount: number; - refetch: (arg?: {variables?: TVariables, merge?: (...args: any[]) => any, disableThrow?: boolean}) => Promise; + refetch: ({ force, throwOnError }?: { force?: boolean; throwOnError?: boolean }) => Promise; } -export interface QueryResultPaginated extends QueryResult { +export interface QueryLoadingResult extends QueryResultBase { + status: 'loading'; + data: TResult | undefined; // even when error, data can have stale data + error: unknown | null; // it still can be error +} + +export interface QueryErrorResult extends QueryResultBase { + status: 'error'; + data: TResult | undefined; // even when error, data can have stale data + error: unknown; +} + +export interface QuerySuccessResult extends QueryResultBase { + status: 'success'; + data: TResult; + error: null; +} + +export type QueryResult = + | QueryLoadingResult + | QueryErrorResult + | QuerySuccessResult; + +export interface PaginatedQueryLoadingResult extends QueryResultBase { + status: 'loading'; + resolvedData: undefined | TResult; // even when error, data can have stale data + latestData: undefined | TResult; // even when error, data can have stale data + error: unknown | null; // it still can be error +} + +export interface PaginatedQueryErrorResult extends QueryResultBase { + status: 'error'; + resolvedData: undefined | TResult; // even when error, data can have stale data + latestData: undefined | TResult; // even when error, data can have stale data + error: unknown; +} + +export interface PaginatedQuerySuccessResult extends QueryResultBase { + status: 'success'; + resolvedData: TResult; + latestData: TResult; + error: null; +} + +export type PaginatedQueryResult = + | PaginatedQueryLoadingResult + | PaginatedQueryErrorResult + | PaginatedQuerySuccessResult; + +export interface InfiniteQueryResult extends QueryResultBase { + data: TResult[]; isFetchingMore: boolean; - canFetchMore: boolean; - fetchMore: (variables?: TVariables) => Promise; + fetchMore: (moreVariable?: TMoreVariable | false) => Promise | undefined; } -export function prefetchQuery( - queryKey: QueryKey, - queryFn: QueryFunction, - options?: PrefetchQueryOptions -): Promise; - -export interface PrefetchQueryOptions extends QueryOptions { - force?: boolean; -} - -export function useMutation( +export function useMutation( mutationFn: MutationFunction, - mutationOptions?: MutationOptions + mutationOptions?: MutationOptions, ): [MutateFunction, MutationResult]; -export type MutationFunction = ( - variables: TVariables, -) => Promise; +export type MutationFunction = (variables: TVariables) => Promise; -export interface MutationOptions { - refetchQueries?: Array; - refetchQueriesOnFailure?: boolean; +export interface MutateOptions { + onSuccess?: (data: TResult, variables: TVariables) => Promise | void; + onError?: (error: unknown, variables: TVariables, snapshotValue: unknown) => Promise | void; + onSettled?: ( + data: undefined | TResult, + error: unknown | null, + variables: TVariables, + snapshotValue?: unknown, + ) => Promise | void; + throwOnError?: boolean; } -export type MutateFunction = ( - variables?: TVariables, - options?: { - updateQuery?: string | [string, object], - waitForRefetchQueries?: boolean; - } -) => Promise; +export interface MutationOptions extends MutateOptions { + onMutate?: (variables: TVariables) => Promise | unknown; + useErrorBoundary?: boolean; +} -export interface MutationResult { - data: TResults | null; - isLoading: boolean; - error: null | Error; - promise: Promise; +export type MutateFunction = undefined extends TVariables + ? (variables?: TVariables, options?: MutateOptions) => Promise + : (variables: TVariables, options?: MutateOptions) => Promise; + +export interface MutationResultBase { + status: 'idle' | 'loading' | 'error' | 'success'; + data: undefined | TResult; + error: null | unknown; + promise: Promise; reset: () => void; } -export function setQueryData( - queryKey: string | [string, object], - data: any, - options?: { - shouldRefetch?: boolean - } -): void | Promise; - -export function refetchQuery( - queryKey: string | [string, object] | [string, false], - force?: { - force?: boolean - } -): Promise; - -export function refetchAllQueries( - options?: { - force?: boolean, - includeInactive: boolean - } -): Promise; - -export function useIsFetching(): boolean; - -export const ReactQueryConfigProvider: React.ComponentType<{ - config?: ReactQueryProviderConfig -}>; - -export interface ReactQueryProviderConfig { - retry?: number; - retryDelay?: (attempt: number) => number; - staleTime?: number; - cacheTime?: number; - refetchAllOnWindowFocus?: boolean; - refetchInterval?: false | number; - suspense?: boolean; +export interface IdleMutationResult extends MutationResultBase { + status: 'idle'; + data: undefined; + error: null; } -export function clearQueryCache(): void; +export interface LoadingMutationResult extends MutationResultBase { + status: 'loading'; + data: undefined; + error: undefined; +} + +export interface ErrorMutationResult extends MutationResultBase { + status: 'error'; + data: undefined; + error: unknown; +} + +export interface SuccessMutationResult extends MutationResultBase { + status: 'success'; + data: TResult; + error: undefined; +} + +export type MutationResult = + | IdleMutationResult + | LoadingMutationResult + | ErrorMutationResult + | SuccessMutationResult; + +export interface CachedQuery { + queryKey: AnyQueryKey; + queryVariables: AnyVariables; + queryFn: (...args: any[]) => unknown; + config: QueryOptions; + state: unknown; + setData(dataOrUpdater: unknown | ((oldData: unknown | undefined) => unknown)): void; +} + +export interface QueryCache { + prefetchQuery( + queryKey: TKey | false | null | undefined | (() => TKey | false | null | undefined), + queryFn: QueryFunction, + config?: QueryOptions, + ): Promise; + + prefetchQuery( + queryKey: TKey | false | null | undefined | (() => TKey | false | null | undefined), + queryFn: QueryFunction, + config?: QueryOptions, + ): Promise; + + prefetchQuery( + queryKey: TKey | false | null | undefined | (() => TKey | false | null | undefined), + variables: TVariables, + queryFn: QueryFunctionWithVariables, + config?: QueryOptions, + ): Promise; + + prefetchQuery( + queryKey: TKey | false | null | undefined | (() => TKey | false | null | undefined), + variables: TVariables, + queryFn: QueryFunctionWithVariables, + config?: QueryOptions, + ): Promise; + + prefetchQuery({ + queryKey, + variables, + queryFn, + config, + }: { + queryKey: TKey | false | null | undefined | (() => TKey | false | null | undefined); + variables?: TVariables; + queryFn: QueryFunctionWithVariables; + config?: QueryOptions; + }): Promise; + + getQueryData(key: AnyQueryKey | string): unknown | undefined; + setQueryData(key: AnyQueryKey | string, dataOrUpdater: unknown | ((oldData: unknown | undefined) => unknown)): void; + refetchQueries( + queryKeyOrPredicateFn: AnyQueryKey | string | ((query: CachedQuery) => boolean), + { exact, throwOnError, force }?: { exact?: boolean; throwOnError?: boolean; force?: boolean }, + ): Promise; + removeQueries( + queryKeyOrPredicateFn: AnyQueryKey | string | ((query: CachedQuery) => boolean), + { exact }?: { exact?: boolean }, + ): Promise; + getQuery(queryKey: AnyQueryKey): CachedQuery | undefined; + getQueries(queryKey: AnyQueryKey): CachedQuery[]; + isFetching: number; + subscribe(callback: (queryCache: QueryCache) => void): () => void; + clear(): CachedQuery[]; +} + +export const queryCache: QueryCache; + +/** + * A hook that returns the number of the quiries that your application is loading or fetching in the background + * (useful for app-wide loading indicators). + * @returns the number of the quiries that your application is currently loading or fetching in the background. + */ +export function useIsFetching(): number; + +export const ReactQueryConfigProvider: React.ComponentType<{ + config?: ReactQueryProviderConfig; +}>; + +export interface ReactQueryProviderConfig extends BaseQueryOptions { + /** Defaults to the value of `suspense` if not defined otherwise */ + useErrorBoundary?: boolean; + throwOnError?: boolean; + refetchAllOnWindowFocus?: boolean; + queryKeySerializerFn?: ( + queryKey: QueryKeyPart[] | string | false | undefined | (() => QueryKeyPart[] | string | false | undefined), + ) => [string, QueryKeyPart[]] | []; + + onMutate?: (variables: unknown) => Promise | unknown; + onSuccess?: (data: unknown, variables?: unknown) => void; + onError?: (err: unknown, snapshotValue?: unknown) => void; + onSettled?: (data: unknown | undefined, error: unknown | null, snapshotValue?: unknown) => void; +} + +export type ConsoleFunction = (...args: any[]) => void; +export interface ConsoleObject { + log: ConsoleFunction; + warn: ConsoleFunction; + error: ConsoleFunction; +} + +export function setConsole(consoleObject: ConsoleObject): void; diff --git a/types/react-query/package.json b/types/react-query/package.json new file mode 100644 index 0000000000..4f31a7f48d --- /dev/null +++ b/types/react-query/package.json @@ -0,0 +1,6 @@ +{ + "private": true, + "dependencies": { + "ts-toolbelt": "*" + } +} diff --git a/types/react-query/react-query-tests.ts b/types/react-query/react-query-tests.ts index 1746d03ab4..7cd42d2b6d 100644 --- a/types/react-query/react-query-tests.ts +++ b/types/react-query/react-query-tests.ts @@ -1,83 +1,316 @@ -import { useMutation, useQuery } from "react-query"; +import { + useMutation, + useQuery, + usePaginatedQuery, + useInfiniteQuery, + useIsFetching, + setConsole, + ReactQueryProviderConfig, +} from 'react-query'; -const queryFn = () => Promise.resolve(); +function simpleQuery() { + // Query - simple case + const querySimple = useQuery('todos', () => Promise.resolve('test')); + querySimple.data; // $ExpectType string | undefined + querySimple.error; // $ExpectType unknown + querySimple.isFetching; // $ExpectType boolean + querySimple.refetch(); // $ExpectType Promise + querySimple.fetchMore; // $ExpectError + querySimple.canFetchMore; // $ExpectError + querySimple.isFetchingMore; // $ExpectError +} -// Query - simple case -const querySimple = useQuery('todos', - () => Promise.resolve('test')); -querySimple.data; // $ExpectType string | null -querySimple.error; // $ExpectType Error | null -querySimple.isLoading; // $ExpectType boolean -querySimple.refetch(); // $ExpectType Promise -queryPaginated.fetchMore; // $ExpectError -queryPaginated.canFetchMore; // $ExpectError -queryPaginated.isFetchingMore; // $ExpectError +function queryWithVariables() { + // Query Variables + const param = 'test'; + const queryVariables = useQuery(['todos', { param }, 10], (key, variables, id) => + Promise.resolve(variables.param === 'test'), + ); -// Query Variables -const param = 'test'; -const queryVariables = useQuery(['todos', {param}], - (variables) => Promise.resolve(variables.param === 'test')); + queryVariables.data; // $ExpectType boolean | undefined + queryVariables.refetch(); // $ExpectType Promise + queryVariables.refetch({ force: true }); // $ExpectType Promise +} -queryVariables.data; // $ExpectType boolean | null -queryVariables.refetch({variables: {param: 'foo'}}); // $ExpectType Promise -queryVariables.refetch({variables: {other: 'foo'}}); // $ExpectError +function invalidSimpleQuery() { + // first element in the key must be a string + useQuery([10, 'a'], async (id, key) => id); // $ExpectError +} -// Query with falsey query key -useQuery(false && ['foo', { bar: 'baz' }], queryFn); +function conditionalQuery(condition: boolean) { + const queryFn1 = (name: string, params: { bar: string }) => Promise.resolve(10); + const queryFn2 = () => Promise.resolve('test'); -// Query with query key function -useQuery(() => ['foo', { bar: 'baz' }], queryFn); + // Query with falsey query key + useQuery(condition && ['foo', { bar: 'baz' }], queryFn1); + useQuery(condition && ['foo', { bar: 'baz' }], queryFn2); + useQuery({ + queryKey: condition && ['foo', { bar: 'baz' }], + queryFn: queryFn1, + }); -// Query with nested variabes -const queryNested = useQuery(['key', { - nested: { - props: [1, 2] + // Query with query key function + useQuery(() => ['foo', { bar: 'baz' }], queryFn1); + useQuery(() => ['foo', { bar: 'baz' }], queryFn2); +} + +function queryWithNestedKey() { + // Query with nested variabes + const queryNested = useQuery( + [ + 'key', + { + nested: { + props: [1, 2], + }, + }, + ], + (key, variables) => Promise.resolve(variables.nested.props[0]), + ); + queryNested.data; // $ExpectType number | undefined +} + +function queryWithComplexKeysAndVariables() { + useQuery(['key', { a: 1 }], [{ b: { x: 1 } }, { c: { x: 1 } }], ( + key1, // $ExpectType string + key2, // ExpectType { a: number } + var1, // $ExpectType { b: { x: number; }; } + var2, // $ExpectType { c: { x: number; }; } + ) => Promise.resolve(key1 === 'key' && key2.a === 1 && var1.b.x === 1 && var2.c.x === 1)); + + // custom key + const longKey: [string, ...number[]] = ['key', 1, 2, 3, 4, 5]; + useQuery( + longKey, + async ( + key, // $ExpectType string + ...ids // $ExpectType number[] + ) => 100, + ).data; // $ExpectType number | undefined + + const longVariables: [boolean, ...object[]] = [true, {}]; + useQuery( + ['key'], + longVariables, + async ( + key, // $ExpectType string + var1, // $ExpectType boolean + ...vars // $ExpectType object[] + ) => 100, + ).data; // $ExpectType number | undefined + + // the following example cannot work properly, as it would require concatenating tuples with infinite tails. + // ts-toolbelt library's `List.Concat` cannot do the job. It would be possible to do with `typescript-tuple` and additional trick. + // useQuery(longKey, longVariables, async ( + // key, // $ExpectType string // <-- currently boolean?! + // keyOrVar, // $ExpectType number | boolean // <-- currently object + // ...rest // $ExpectType number | object // <-- currently object[] + // ) => 100).data; // $ExpectType number | undefined +} + +function paginatedQuery() { + // Paginated mode + const queryPaginated = usePaginatedQuery('key', () => Promise.resolve({ data: [1, 2, 3], next: true }), { + refetchInterval: 1000, + }); + queryPaginated.resolvedData; // $ExpectType { data: number[]; next: boolean; } | undefined + queryPaginated.latestData; // $ExpectType { data: number[]; next: boolean; } | undefined + queryPaginated.data; // $ExpectError + + // Discriminated union over status + if (queryPaginated.status === 'loading') { + queryPaginated.resolvedData; // $ExpectType { data: number[]; next: boolean; } | undefined + queryPaginated.latestData; // $ExpectType { data: number[]; next: boolean; } | undefined + queryPaginated.error; // $ExpectType unknown } -}], (variables) => Promise.resolve(variables.nested.props[0])); -queryNested.data; // $ExpectType number | null -// Paginated mode -const queryPaginated = useQuery('key', () => Promise.resolve({data: [1, 2, 3], next: true}), { - refetchInterval: 1000, - paginated: true, - getCanFetchMore: (lastPage, allPages) => lastPage.next -}); -queryPaginated.data; // $ExpectType { data: number[]; next: boolean; }[] | null -queryPaginated.fetchMore; // $ExpectType (variables?: {} | undefined) => Promise<{ data: number[]; next: boolean; }> || (variables?: object | undefined) => Promise<{ data: number[]; next: boolean; }> -queryPaginated.canFetchMore; // $ExpectType boolean -queryPaginated.isFetchingMore; // $ExpectType boolean + if (queryPaginated.status === 'error') { + queryPaginated.resolvedData; // $ExpectType { data: number[]; next: boolean; } | undefined + queryPaginated.latestData; // $ExpectType { data: number[]; next: boolean; } | undefined + queryPaginated.error; // $ExpectType unknown + } -// Paginated mode - check if getCanFetchMore is required -useQuery('key', () => Promise.resolve(), {paginated: true}); // $ExpectError + if (queryPaginated.status === 'success') { + queryPaginated.resolvedData; // $ExpectType { data: number[]; next: boolean; } + queryPaginated.latestData; // $ExpectType { data: number[]; next: boolean; } + queryPaginated.error; // $ExpectType null + } +} -// Simple mutation -const mutation = () => Promise.resolve(['foo', 1]); +function simpleInfiniteQuery(condition: boolean) { + async function fetchWithCursor(key: string, cursor?: string) { + return [1, 2, 3]; + } + function getFetchMore(last: number[], all: number[][]) { + return last.length ? String(all.length + 1) : false; + } -const [mutate] = useMutation(mutation, { - refetchQueries: ['todos', ['todo', { id: 5 }], 'reminders'], - refetchQueriesOnFailure: false -}); -mutate(); -mutate(undefined, { - updateQuery: 'query', - waitForRefetchQueries: false -}); + useInfiniteQuery(['key'], fetchWithCursor, { + getFetchMore: ( + last, // $ExpectType number[] + all, // $ExpectType number[][] + ) => 'next', + }); + useInfiniteQuery(['key'], fetchWithCursor, { getFetchMore }); + useInfiniteQuery('key', fetchWithCursor, { getFetchMore }); + useInfiniteQuery(() => condition && 'key', fetchWithCursor, { getFetchMore }); -// Invalid mutatation funciton -useMutation((arg1: string, arg2: string) => Promise.resolve()); // $ExpectError -useMutation((arg1: string) => null); // $ExpectError + const infiniteQuery = useInfiniteQuery(['key'], fetchWithCursor, { getFetchMore }); -// Mutation with variables -const [mutateWithVars] = useMutation( - ({param}: {param: number}) => Promise.resolve(Boolean(param)), - { - refetchQueries: ['todos', ['todo', { id: 5 }], 'reminders'], - refetchQueriesOnFailure: false -}); + // The next example does not work; the type for cursor does not get inferred. + // useInfiniteQuery(['key'], fetchWithCursor, { + // getFetchMore: (last, all) => 'string', + // }); -mutateWithVars({param: 1}, { - updateQuery: 'query', - waitForRefetchQueries: false -}); + infiniteQuery.data; // $ExpectType number[][] + infiniteQuery.fetchMore(); // $ExpectType Promise | undefined + infiniteQuery.fetchMore('next'); // $ExpectType Promise | undefined +} -mutateWithVars({param: 'test'}); // $ExpectError +function log(...args: any[]) {} + +function infiniteQueryWithVariables(condition: boolean) { + async function fetchWithCursor2(key: string, debuglog?: (...args: any[]) => void, cursor?: string) { + if (debuglog) debuglog(key, cursor); + return [1, 2, 3]; + } + function getFetchMore(last: number[], all: number[][]) { + return last.length ? String(all.length + 1) : false; + } + + useInfiniteQuery void)], string>( + ['key'], + [undefined], + fetchWithCursor2, + { + getFetchMore: (last, all) => 'next', + }, + ); + + useInfiniteQuery(['key'], [log], fetchWithCursor2, { getFetchMore }); + useInfiniteQuery('key', [log], fetchWithCursor2, { getFetchMore }); + useInfiniteQuery(() => condition && 'key', [log], fetchWithCursor2, { getFetchMore }); +} + +function simpleMutation() { + // Simple mutation + const mutation = () => Promise.resolve(['foo', 'bar']); + + const [mutate] = useMutation(mutation, { + onSuccess(result) { + result; // $ExpectType string[] + }, + }); + mutate(); + mutate(undefined, { + throwOnError: true, + onSettled(result, error) { + result; // $ExpectType string[] | undefined + error; // $ExpectType unknown + }, + }); + + // Invalid mutatation funciton + useMutation((arg1: string, arg2: string) => Promise.resolve()); // $ExpectError + useMutation((arg1: string) => null); // $ExpectError +} + +function mutationWithVariables() { + // Mutation with variables + const [mutateWithVars] = useMutation(({ param }: { param: number }) => Promise.resolve(Boolean(param)), { + useErrorBoundary: true, + onMutate(variables) { + variables; // $ExpectType { param: number; } + return { snapshot: variables.param }; + }, + }); + + mutateWithVars( + { param: 1 }, + { + async onSuccess(data) { + data; // $ExpectType boolean + }, + }, + ); + + mutateWithVars({ param: 'test' }); // $ExpectError +} + +function helpers() { + useIsFetching(); // $ExpectType number + + setConsole({ log, error: log, warn: log }); +} + +function globalConfig() { + const globalConfig: ReactQueryProviderConfig = { + onError(err, snapshot) { + log('Error', err, snapshot); + }, + onMutate(variables) { + log(variables); + }, + suspense: true, + }; +} + +function dataDiscriminatedUnion() { + // Query Variables + const param = 'test'; + const queryResult = useQuery(['todos', { param }], (key, variables) => Promise.resolve([param])); + + queryResult.data; // $ExpectType string[] | undefined + + // Discriminated union over status + if (queryResult.status === 'loading') { + queryResult.data; // $ExpectType string[] | undefined + queryResult.error; // $ExpectType unknown + } + + if (queryResult.status === 'error') { + // disabled + queryResult.data; // $ExpectType string[] | undefined + queryResult.error; // $ExpectType unknown + } + + if (queryResult.status === 'success') { + // disabled + queryResult.data; // $ExpectType string[] + queryResult.error; // $ExpectType null + } +} + +function mutationStatusDiscriminatedUnion() { + const mutation = () => Promise.resolve(['foo', 'bar']); + const [mutate, mutationState] = useMutation(mutation); + mutate(); + // enabled + // TODO: handle invalid argument passed to mutationFn + // mutate('arg'); // $ExpectError + mutate('arg'); // $ExpectError + mutationState.data; // $ExpectType string[] | undefined + + // Discriminated union over status + if (mutationState.status === 'idle') { + mutationState.data; // $ExpectType undefined + mutationState.error; // $ExpectType null + } + + if (mutationState.status === 'loading') { + mutationState.data; // $ExpectType undefined + // corrected + // mutationState.error; // $ExpectType null + mutationState.error; // $ExpectType undefined + } + + if (mutationState.status === 'error') { + mutationState.data; // $ExpectType undefined + mutationState.error; // $ExpectType unknown + } + + if (mutationState.status === 'success') { + mutationState.data; // $ExpectType string[] + mutationState.error; // $ExpectType undefined + } +}