import { useInfiniteQuery as useInfiniteReactQuery, UseInfiniteQueryOptions, UseInfiniteQueryResult, useQuery as useReactQuery, UseQueryOptions, UseQueryResult, MutateOptions, useMutation as useReactQueryMutation, UseMutationOptions, UseMutationResult, } from 'react-query' import { useSession } from './auth' import { FirstParam, PromiseReturnType, AsyncFunc } from '../types/utils' import { isServer } from '../stdlib/index' import { emptyQueryFn, getQueryCacheFunctions, getQueryKey, QueryCacheFunctions, sanitizeQuery, sanitizeMutation, getInfiniteQueryKey, } from './react-query-utils' import { useRouter } from '../client/router' type QueryLazyOptions = { suspense: unknown } | { enabled: unknown } type QueryNonLazyOptions = | { suspense: true; enabled?: never } | { suspense?: never; enabled: true } | { suspense: true; enabled: true } | { suspense?: never; enabled?: never } // ------------------------- // useQuery // ------------------------- type RestQueryResult = Omit< UseQueryResult, 'data' > & QueryCacheFunctions export function useQuery< T extends AsyncFunc, TResult = PromiseReturnType, TError = unknown, TSelectedData = TResult >( queryFn: T, params: FirstParam, options?: UseQueryOptions & QueryNonLazyOptions ): [TSelectedData, RestQueryResult] export function useQuery< T extends AsyncFunc, TResult = PromiseReturnType, TError = unknown, TSelectedData = TResult >( queryFn: T, params: FirstParam, options: UseQueryOptions & QueryLazyOptions ): [TSelectedData | undefined, RestQueryResult] export function useQuery< T extends AsyncFunc, TResult = PromiseReturnType, TError = unknown, TSelectedData = TResult >( queryFn: T, params: FirstParam, options: UseQueryOptions = {} ) { if (typeof queryFn === 'undefined') { throw new Error( 'useQuery is missing the first argument - it must be a query function' ) } const suspenseEnabled = Boolean(process.env.__BLITZ_SUSPENSE_ENABLED) let enabled = isServer && suspenseEnabled ? false : options?.enabled ?? options?.enabled !== null const suspense = enabled === false ? false : options?.suspense const session = useSession({ suspense }) if (session.isLoading) { enabled = false } const routerIsReady = useRouter().isReady || (isServer && suspenseEnabled) const enhancedResolverRpcClient = sanitizeQuery(queryFn) const queryKey = getQueryKey(queryFn, params) const { data, ...queryRest } = useReactQuery({ queryKey: routerIsReady ? queryKey : ['_routerNotReady_'], queryFn: routerIsReady ? () => enhancedResolverRpcClient(params, { fromQueryHook: true }) : (emptyQueryFn as any), ...options, enabled, }) if ( queryRest.isIdle && isServer && suspenseEnabled !== false && !data && (!options || !('suspense' in options) || options.suspense) && (!options || !('enabled' in options) || options.enabled) ) { throw new Promise(() => {}) } const rest = { ...queryRest, ...getQueryCacheFunctions, TResult, T>(queryFn, params), } // return [data, rest as RestQueryResult] return [data, rest] } // ------------------------- // usePaginatedQuery // ------------------------- type RestPaginatedResult = Omit< UseQueryResult, 'data' > & QueryCacheFunctions export function usePaginatedQuery< T extends AsyncFunc, TResult = PromiseReturnType, TError = unknown, TSelectedData = TResult >( queryFn: T, params: FirstParam, options?: UseQueryOptions & QueryNonLazyOptions ): [TSelectedData, RestPaginatedResult] export function usePaginatedQuery< T extends AsyncFunc, TResult = PromiseReturnType, TError = unknown, TSelectedData = TResult >( queryFn: T, params: FirstParam, options: UseQueryOptions & QueryLazyOptions ): [TSelectedData | undefined, RestPaginatedResult] export function usePaginatedQuery< T extends AsyncFunc, TResult = PromiseReturnType, TError = unknown, TSelectedData = TResult >( queryFn: T, params: FirstParam, options: UseQueryOptions = {} ) { if (typeof queryFn === 'undefined') { throw new Error( 'usePaginatedQuery is missing the first argument - it must be a query function' ) } const suspenseEnabled = Boolean(process.env.__BLITZ_SUSPENSE_ENABLED) let enabled = isServer && suspenseEnabled ? false : options?.enabled ?? options?.enabled !== null const suspense = enabled === false ? false : options?.suspense const session = useSession({ suspense }) if (session.isLoading) { enabled = false } const routerIsReady = useRouter().isReady || (isServer && suspenseEnabled) const enhancedResolverRpcClient = sanitizeQuery(queryFn) const queryKey = getQueryKey(queryFn, params) const { data, ...queryRest } = useReactQuery({ queryKey: routerIsReady ? queryKey : ['_routerNotReady_'], queryFn: routerIsReady ? () => enhancedResolverRpcClient(params, { fromQueryHook: true }) : (emptyQueryFn as any), ...options, keepPreviousData: true, enabled, }) if ( queryRest.isIdle && isServer && suspenseEnabled !== false && !data && (!options || !('suspense' in options) || options.suspense) && (!options || !('enabled' in options) || options.enabled) ) { throw new Promise(() => {}) } const rest = { ...queryRest, ...getQueryCacheFunctions, TResult, T>(queryFn, params), } // return [data, rest as RestPaginatedResult] return [data, rest] } // ------------------------- // useInfiniteQuery // ------------------------- interface RestInfiniteResult extends Omit, 'data'>, QueryCacheFunctions { pageParams: any } interface InfiniteQueryConfig extends UseInfiniteQueryOptions { // getPreviousPageParam?: (lastPage: TResult, allPages: TResult[]) => TGetPageParamResult // getNextPageParam?: (lastPage: TResult, allPages: TResult[]) => TGetPageParamResult } export function useInfiniteQuery< T extends AsyncFunc, TResult = PromiseReturnType, TError = unknown, TSelectedData = TResult >( queryFn: T, getQueryParams: (pageParam: any) => FirstParam, options: InfiniteQueryConfig & QueryNonLazyOptions ): [TSelectedData[], RestInfiniteResult] export function useInfiniteQuery< T extends AsyncFunc, TResult = PromiseReturnType, TError = unknown, TSelectedData = TResult >( queryFn: T, getQueryParams: (pageParam: any) => FirstParam, options: InfiniteQueryConfig & QueryLazyOptions ): [TSelectedData[] | undefined, RestInfiniteResult] export function useInfiniteQuery< T extends AsyncFunc, TResult = PromiseReturnType, TError = unknown, TSelectedData = TResult >( queryFn: T, getQueryParams: (pageParam: any) => FirstParam, options: InfiniteQueryConfig ) { if (typeof queryFn === 'undefined') { throw new Error( 'useInfiniteQuery is missing the first argument - it must be a query function' ) } const suspenseEnabled = Boolean(process.env.__BLITZ_SUSPENSE_ENABLED) let enabled = isServer && suspenseEnabled ? false : options?.enabled ?? options?.enabled !== null const suspense = enabled === false ? false : options?.suspense const session = useSession({ suspense }) if (session.isLoading) { enabled = false } const routerIsReady = useRouter().isReady || (isServer && suspenseEnabled) const enhancedResolverRpcClient = sanitizeQuery(queryFn) const queryKey = getInfiniteQueryKey(queryFn, getQueryParams) const { data, ...queryRest } = useInfiniteReactQuery({ // we need an extra cache key for infinite loading so that the cache for // for this query is stored separately since the hook result is an array of results. // Without this cache for usePaginatedQuery and this will conflict and break. queryKey: routerIsReady ? queryKey : ['_routerNotReady_'], queryFn: routerIsReady ? ({ pageParam }) => enhancedResolverRpcClient(getQueryParams(pageParam), { fromQueryHook: true, }) : (emptyQueryFn as any), ...options, enabled, }) if ( queryRest.isIdle && isServer && suspenseEnabled !== false && !data && (!options || !('suspense' in options) || options.suspense) && (!options || !('enabled' in options) || options.enabled) ) { throw new Promise(() => {}) } const rest = { ...queryRest, ...getQueryCacheFunctions, TResult, T>( queryFn, getQueryParams ), pageParams: data?.pageParams, } return [data?.pages as any, rest] } // ------------------------------------------------------------------- // useMutation // ------------------------------------------------------------------- /* * We have to override react-query's MutationFunction and MutationResultPair * types so because we have throwOnError:true by default. And by the RQ types * have the mutate function result typed as TData|undefined which isn't typed * properly with throwOnError. * * So this fixes that. */ export declare type MutateFunction< TData, TError = unknown, TVariables = unknown, TContext = unknown > = ( variables?: TVariables, config?: MutateOptions ) => Promise export declare type MutationResultPair = [ MutateFunction, Omit, 'mutate' | 'mutateAsync'> ] export declare type MutationFunction = ( variables: TVariables, ctx?: any ) => Promise export function useMutation< TData = unknown, TError = unknown, TVariables = void, TContext = unknown >( mutationResolver: MutationFunction, config?: UseMutationOptions ): MutationResultPair { const enhancedResolverRpcClient = sanitizeMutation(mutationResolver) const { mutate, mutateAsync, ...rest } = useReactQueryMutation< TData, TError, TVariables, TContext >( (variables) => enhancedResolverRpcClient(variables, { fromQueryHook: true }), { throwOnError: true, ...config, } as any ) return [mutateAsync, rest] as MutationResultPair< TData, TError, TVariables, TContext > }