import { useCallback , useEffect , useRef , useState } from 'react' ; import { useSelector } from 'react-redux' ; import isEqual from 'lodash/isEqual' ; import useDataProvider from './useDataProvider' ; import useVersion from '../controller/useVersion' ; import getFetchType from './getFetchType' ; import { useSafeSetState } from '../util/hooks' ; import { ReduxState , OnSuccess , OnFailure , DataProvider } from '../types' ; export interface DataProviderQuery { type : string ; resource : string ; payload : object ; } export type Refetch = ( ) => void ; export interface UseQueryWithStoreValue { data ? : any ; total ? : number ; error ? : any ; loading : boolean ; loaded : boolean ; refetch : Refetch ; } export interface QueryOptions { onSuccess ? : OnSuccess ; onFailure ? : OnFailure ; action ? : string ; enabled ? : boolean ; [ key ] : any ; } type PartialQueryState = { error ? : any ; loading : boolean ; loaded : boolean ; } ; const queriesThisTick = { } ; /**
 * Default cache selector. Allows to cache responses by default.
 *
 * By default, custom queries are dispatched as a CUSTOM_QUERY Redux action.
 * The useDataProvider hook dispatches a CUSTOM_QUERY_SUCCESS when the response
 * comes, and the customQueries reducer stores the result in the store.
 * This selector reads the customQueries store and acts as a response cache.
 */ const defaultDataSelector = query => ( state ) => { const key = JSON . stringify ( { ... query , type : getFetchType ( query . type ) } ) ; return state . admin . customQueries [ key ] ? state . admin . customQueries [ key ] . data : undefined ; } ; const defaultTotalSelector = query => ( state ) => { const key = JSON . stringify ( { ... query , type : getFetchType ( query . type ) } ) ; return state . admin . customQueries [ key ] ? state . admin . customQueries [ key ] . total : null ; } ; const defaultIsDataLoaded = ( data ) : boolean => data !== undefined ; param query param { string } query . type param { string } query . resource param { Object } query . payload param options param { string } options . action param { boolean } options . enabled param { Function } options . onSuccess param { Function } options . onFailure param { Function } dataSelector param { Function } totalSelector param { Function } isDataLoaded returns example export const useQueryWithStore = < State extends ReduxState = ReduxState , TDataProvider extends DataProvider = DataProvider > ( query , options = { action : 'CUSTOM_QUERY' } , dataSelector = defaultDataSelector ( query ) , totalSelector = defaultTotalSelector ( query ) , isDataLoaded = defaultIsDataLoaded ) : UseQueryWithStoreValue => { const { type , resource , payload } = query ; const version = useVersion ( ) ; const [ innerVersion , setInnerVersion ] = useState ( 0 ) ; const requestSignature = JSON . stringify ( { query , options , version , innerVersion , } ) ; const requestSignatureRef = useRef ( requestSignature ) ; const data = useSelector ( dataSelector ) ; const total = useSelector ( totalSelector ) ; const refetch = useCallback ( ( ) => { setInnerVersion ( prevInnerVersion => prevInnerVersion + 1 ) ; } , [ ] ) ; const [ state , setState ] = useSafeSetState ( { data , total , error : null , loading : options ?. enabled === false ? false : true , loaded : options ?. enabled === false ? false : isDataLoaded ( data ) , refetch , } ) ; useEffect ( ( ) => { if ( requestSignatureRef . current !== requestSignature ) { requestSignatureRef . current = requestSignature ; setState ( { data , total , error : null , loading : options ?. enabled === false ? false : true , loaded : options ?. enabled === false ? false : isDataLoaded ( data ) , refetch , } ) ; } else if ( ! isEqual ( state . data , data ) || state . total !== total ) { if ( typeof total !== 'undefined' && isNaN ( total ) ) { console . error ( 'Total from response is not a number. Please check your dataProvider or the API.' ) ; } else { setState ( prevState => ( { ... prevState , data , total , loaded : true , loading : false , } ) ) ; } } } , [ data , requestSignature , setState , state . data , state . total , total , isDataLoaded , refetch , options . enabled , ] ) ; const dataProvider = useDataProvider < TDataProvider > ( ) ; useEffect ( ( ) => { if ( ! queriesThisTick . hasOwnProperty ( requestSignature ) ) { queriesThisTick [ requestSignature ] = new Promise < PartialQueryState > ( resolve => { dataProvider [ type ] ( resource , payload , options ) . then ( ( ) => { if ( requestSignature !== requestSignatureRef . current ) { resolve ( undefined ) ; } resolve ( { error : null , loading : false , loaded : options ?. enabled === false ? false : true , } ) ; } ) . catch ( error => { if ( requestSignature !== requestSignatureRef . current ) { resolve ( undefined ) ; } resolve ( { error , loading : false , loaded : false , } ) ; } ) ; } ) ; setTimeout ( ( ) => { delete queriesThisTick [ requestSignature ] ; } , 0 ) ; } ( async ( ) => { const newState = await queriesThisTick [ requestSignature ] ; if ( newState ) setState ( state => ( { ... state , ... newState } ) ) ; } ) ( ) ; } , [ requestSignature ] ) ; return state ; } ;