import type { GQtyClient } from 'gqty' ; import * as React from 'react' ; import { coreHelpers , CoreHelpers , FetchPolicy , useSelectionsState , useSubscribeCacheChanges , useSuspensePromise , uniqBy , sortBy , } from '../common' ; import type { ReactClientOptionsWithDefaults } from '../utils' ; export type PaginatedQueryFetchPolicy = Extract < FetchPolicy , 'cache-first' | 'cache-and-network' | 'network-only' > ; export interface UsePaginatedQueryMergeParams < TData > { data : { existing : TData | undefined ; incoming : TData ; } ; uniqBy : typeof uniqBy ; sortBy : typeof sortBy ; } export interface UsePaginatedQueryOptions < TData , TArgs > { /**
     * Initial arguments used on first request
     */ initialArgs : TArgs ; /**
     * Custom merge function
     */ merge ? : ( params : UsePaginatedQueryMergeParams < TData > ) => TData | undefined | void ; /**
     * Fetch Policy behavior
     *
     * If using `cache-and-network` and `merge`, we recomend using the `uniqBy` helper included inside the `merge` parameters.
     */ fetchPolicy ? : PaginatedQueryFetchPolicy ; default skip ? : boolean ; /**
     * Activate suspense on first call
     */ suspense ? : boolean ; } export interface UsePaginatedQueryData < TData , TArgs > { /**
     * Query Data
     */ data : TData | undefined ; /**
     * Current arguments used in the query
     */ args : TArgs ; /**
     * Network fetch is loading
     */ isLoading : boolean ; /**
     * Main function to be used
     *
     * If new args are not specified, the previous or initial args are used
     *
     * In the second parameter you can override the `"fetchPolicy"`, for example you can set it to `"network-only"` to do a refetch.
     */ fetchMore : ( /**
     * Optional new args. It can receive a function that receives the previous data/args and returns the new args, or the new args directly
     *
     * If not specified or `undefined`, the previous or initial args are used.
     */ newArgs ? : ( ( data : FetchMoreCallbackArgs < TData , TArgs > ) => TArgs ) | TArgs | undefined , /**
     * Override hook fetchPolicy
     */ fetchPolicy ? : PaginatedQueryFetchPolicy ) => Promise < TData > | TData ; /**
     * Has the function been called
     */ called : boolean ; } interface UsePaginatedQueryState < TData , TArgs > { data : TData | undefined ; args : TArgs ; isLoading : boolean ; called : boolean ; } type UsePaginatedQueryReducerAction < TData > = { type : 'loading' ; } | { type : 'cache_found' ; payload : TData ; } | { type : 'data' ; payload : TData ; } ; function UsePaginatedQueryReducer < TData , TArgs > ( state , action ) { switch ( action . type ) { case 'loading' : { if ( state . isLoading ) return state ; return { ... state , isLoading : true , called : true , } ; } case 'cache_found' : { return { data : action . payload , args : state . args , isLoading : true , called : true , } ; } case 'data' : { return { data : action . payload , args : state . args , isLoading : false , called : true , } ; } default : return state ; } } function InitUsePaginatedQueryReducer < TData , TArgs > ( opts ) { return { data : undefined , args : opts . initialArgs , isLoading : ! opts . skip , called : false , } ; } export interface FetchMoreCallbackArgs < TData , TArgs > { existingData : TData | undefined ; existingArgs : TArgs ; } export interface UsePaginatedQuery < GeneratedSchema extends { query : object ; mutation : object ; subscription : object ; } > { < TData , TArgs extends Record < string , any > | string | number | null > ( fn , options ) : UsePaginatedQueryData < TData , TArgs > ; } export function createUsePaginatedQuery < GeneratedSchema extends { query : object ; mutation : object ; subscription : object ; } > ( { query : clientQuery , inlineResolved , eventHandler , } , { defaults : { paginatedQueryFetchPolicy : defaultFetchPolicy , paginatedQuerySuspense : defaultSuspense , } , } ) { function usePaginatedQuery < TData , TArgs extends Record < string , any > | string | number | null > ( fn , opts ) { const fnRef = React . useRef ( fn ) ; fnRef . current = fn ; const optsRef = React . useRef ( opts ) ; optsRef . current = Object . assign ( { } , opts ) ; optsRef . current . fetchPolicy ??= defaultFetchPolicy ; optsRef . current . suspense ??= defaultSuspense ; const [ state , dispatch ] = React . useReducer ( UsePaginatedQueryReducer , opts , InitUsePaginatedQueryReducer ) as [ UsePaginatedQueryState < TData , TArgs > , React . Dispatch < UsePaginatedQueryReducerAction < TData > > ] ; const hookSelections = useSelectionsState ( ) ; const stateRef = React . useRef ( state ) ; stateRef . current = state ; const setSuspensePromise = useSuspensePromise ( optsRef ) ; const isMerging = React . useRef ( 0 ) ; const fetchMore = React . useCallback ( ( newArgs ? , fetchPolicy = optsRef . current . fetchPolicy || defaultFetchPolicy ) => { function mergeData ( incomingData ) { let mergeResult ; if ( optsRef . current . merge ) { const params = { data : { incoming : incomingData , existing : stateRef . current . data , } , uniqBy , sortBy , } ; try { ++ isMerging . current ; mergeResult = optsRef . current . merge ( params ) ; } finally { Promise . resolve ( ) . then ( ( ) => -- isMerging . current ) ; } } return mergeResult === undefined ? incomingData : mergeResult ; } let args = newArgs !== undefined ? typeof newArgs === 'function' ? ( stateRef . current . args = ( newArgs as ( data : FetchMoreCallbackArgs < TData , TArgs > ) => TArgs ) ( { existingData : stateRef . current . data , existingArgs : stateRef . current . args , } ) ) : ( stateRef . current . args = newArgs ) : stateRef . current . args ; const resolvedFn = ( ) => fnRef . current ( clientQuery , args , coreHelpers ) ; const refetch = fetchPolicy !== 'cache-first' ; let incomingData = inlineResolved ( resolvedFn , { onSelection ( selection ) { hookSelections . add ( selection ) ; } , refetch , onCacheData ( data ) { if ( fetchPolicy === 'cache-and-network' ) { const payload = mergeData ( data ) ; stateRef . current . data = payload ; dispatch ( { type : 'cache_found' , payload , } ) ; } } , } ) ; if ( incomingData instanceof Promise ) { dispatch ( { type : 'loading' , } ) ; return incomingData . then ( ( incomingData ) => { const payload = mergeData ( incomingData ) ; stateRef . current . data = payload ; dispatch ( { type : 'data' , payload , } ) ; return payload ; } ) ; } const payload = mergeData ( incomingData ) ; stateRef . current . data = payload ; dispatch ( { type : 'data' , payload , } ) ; return payload ; } , [ stateRef , fnRef , dispatch , optsRef , setSuspensePromise ] ) ; useSubscribeCacheChanges ( { hookSelections , eventHandler , onChange ( ) { if ( isMerging . current ) return ; fetchMore ( undefined , 'cache-first' ) ; } , } ) ; if ( ! state . called && ! opts . skip ) { state . called = true ; const result = fetchMore ( ) ; if ( result instanceof Promise ) { const catchedPromise = result . catch ( console . error ) ; if ( state . data === undefined ) { Promise . resolve ( ) . then ( ( ) => { setSuspensePromise ( catchedPromise ) ; } ) ; } } } return React . useMemo ( ( ) => { return Object . assign ( state , { fetchMore , } ) ; } , [ state , fetchMore ] ) ; } return usePaginatedQuery ; }