import { useCallback , useMemo , useEffect , useState , useRef } from 'react' ; import { useSelector , useDispatch , shallowEqual } from 'react-redux' ; import { parse , stringify } from 'query-string' ; import lodashDebounce from 'lodash/debounce' ; import pickBy from 'lodash/pickBy' ; import { useHistory , useLocation } from 'react-router-dom' ; import queryReducer , { SET_FILTER , HIDE_FILTER , SHOW_FILTER , SET_PAGE , SET_PER_PAGE , SET_SORT , SORT_ASC , } from '../reducer/admin/resource/list/queryReducer' ; import { changeListParams , ListParams } from '../actions/listActions' ; import { SortPayload , ReduxState , FilterPayload } from '../types' ; import removeEmpty from '../util/removeEmpty' ; interface ListParamsOptions { resource : string ; perPage ? : number ; sort ? : SortPayload ; filterDefaultValues ? : FilterPayload ; debounce ? : number ; syncWithLocation ? : boolean ; } interface Parameters extends ListParams { filterValues : object ; displayedFilters : { [ key ] : boolean ; } ; requestSignature : any [ ] ; } interface Modifiers { changeParams : ( action : any ) => void ; setPage : ( page : number ) => void ; setPerPage : ( pageSize : number ) => void ; setSort : ( sort : string , order ? : string ) => void ; setFilters : ( filters : any , displayedFilters : any ) => void ; hideFilter : ( filterName : string ) => void ; showFilter : ( filterName : string , defaultValue : any ) => void ; } const emptyObject = { } ; const defaultSort = { field : 'id' , order : SORT_ASC , } ; const defaultParams = { } ; returns { Array } example const useListParams = ( { resource , filterDefaultValues , sort = defaultSort , perPage = 10 , debounce = 500 , syncWithLocation = false , } ) : [ Parameters , Modifiers ] => { const dispatch = useDispatch ( ) ; const location = useLocation ( ) ; const history = useHistory ( ) ; const [ localParams , setLocalParams ] = useState ( defaultParams ) ; const params = useSelector ( ( reduxState ) => reduxState . admin . resources [ resource ] ? reduxState . admin . resources [ resource ] . list . params : defaultParams , shallowEqual ) ; const tempParams = useRef < ListParams > ( ) ; const requestSignature = [ location . search , resource , syncWithLocation ? params : localParams , filterDefaultValues , JSON . stringify ( sort ) , perPage , syncWithLocation , ] ; const queryFromLocation = syncWithLocation ? parseQueryFromLocation ( location ) : { } ; const query = useMemo ( ( ) => getQuery ( { queryFromLocation , params : syncWithLocation ? params : localParams , filterDefaultValues , sort , perPage , } ) , requestSignature ) ; useEffect ( ( ) => { if ( Object . keys ( queryFromLocation ) . length > 0 ) { dispatch ( changeListParams ( resource , query ) ) ; } } , [ location . search ] ) ; const changeParams = useCallback ( action => { if ( ! tempParams . current ) { tempParams . current = queryReducer ( query , action ) ; setTimeout ( ( ) => { if ( syncWithLocation ) { history . push ( { search : `?${ stringify ( { ... tempParams . current , filter : JSON . stringify ( tempParams . current . filter ) , displayedFilters : JSON . stringify ( tempParams . current . displayedFilters ) , } ) }` , state : { _scrollToTop : action . type === SET_PAGE } , } ) ; } else { setLocalParams ( tempParams . current ) ; } tempParams . current = undefined ; } , 0 ) ; } else { tempParams . current = queryReducer ( tempParams . current , action ) ; } } , requestSignature ) ; const setSort = useCallback ( ( sort , order ? ) => changeParams ( { type : SET_SORT , payload : { sort , order } , } ) , requestSignature ) ; const setPage = useCallback ( ( newPage ) => changeParams ( { type : SET_PAGE , payload : newPage } ) , requestSignature ) ; const setPerPage = useCallback ( ( newPerPage ) => changeParams ( { type : SET_PER_PAGE , payload : newPerPage } ) , requestSignature ) ; const filterValues = query . filter || emptyObject ; const displayedFilterValues = query . displayedFilters || emptyObject ; const debouncedSetFilters = lodashDebounce ( ( filter , displayedFilters ) => { changeParams ( { type : SET_FILTER , payload : { filter : removeEmpty ( filter ) , displayedFilters , } , } ) ; } , debounce ) ; const setFilters = useCallback ( ( filter , displayedFilters , debounce = true ) => debounce ? debouncedSetFilters ( filter , displayedFilters ) : changeParams ( { type : SET_FILTER , payload : { filter : removeEmpty ( filter ) , displayedFilters , } , } ) , requestSignature ) ; const hideFilter = useCallback ( ( filterName ) => { changeParams ( { type : HIDE_FILTER , payload : filterName , } ) ; } , requestSignature ) ; const showFilter = useCallback ( ( filterName , defaultValue ) => { changeParams ( { type : SHOW_FILTER , payload : { filterName , defaultValue , } , } ) ; } , requestSignature ) ; return [ { displayedFilters : displayedFilterValues , filterValues , requestSignature , ... query , } , { changeParams , setPage , setPerPage , setSort , setFilters , hideFilter , showFilter , } , ] ; } ; export const validQueryParams = [ 'page' , 'perPage' , 'sort' , 'order' , 'filter' , 'displayedFilters' , ] ; const parseObject = ( query , field ) => { if ( query [ field ] && typeof query [ field ] === 'string' ) { try { query [ field ] = JSON . parse ( query [ field ] ) ; } catch ( err ) { delete query [ field ] ; } } } ; export const parseQueryFromLocation = ( { search } ) : Partial < ListParams > => { const query = pickBy ( parse ( search ) , ( v , k ) => validQueryParams . indexOf ( k ) !== - 1 ) ; parseObject ( query , 'filter' ) ; parseObject ( query , 'displayedFilters' ) ; return query ; } ; param { Object } params export const hasCustomParams = ( params ) => { return ( params && params . filter && ( Object . keys ( params . filter ) . length > 0 || params . order != null || params . page !== 1 || params . perPage != null || params . sort != null ) ) ; } ; /**
 * Merge list params from 3 different sources:
 *   - the query string
 *   - the params stored in the state (from previous navigation)
 *   - the props passed to the List component (including the filter defaultValues)
 */ export const getQuery = ( { queryFromLocation , params , filterDefaultValues , sort , perPage , } ) => { const query = Object . keys ( queryFromLocation ) . length > 0 ? queryFromLocation : hasCustomParams ( params ) ? { ... params } : { filter : filterDefaultValues || { } } ; if ( ! query . sort ) { query . sort = sort . field ; query . order = sort . order ; } if ( query . perPage == null ) { query . perPage = perPage ; } if ( query . page == null ) { query . page = 1 ; } return { ... query , page : getNumberOrDefault ( query . page , 1 ) , perPage : getNumberOrDefault ( query . perPage , 10 ) , } as ListParams ; } ; export const getNumberOrDefault = ( possibleNumber , defaultValue ) => { const parsedNumber = typeof possibleNumber === 'string' ? parseInt ( possibleNumber , 10 ) : possibleNumber ; return isNaN ( parsedNumber ) ? defaultValue : parsedNumber ; } ; export default useListParams ;