mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
140 lines
5.4 KiB
TypeScript
140 lines
5.4 KiB
TypeScript
import type { UseQueryOptions } from '@tanstack/react-query';
|
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
import { pick, omit } from 'lodash';
|
|
import { useRouter } from 'next/router';
|
|
import React, { useCallback } from 'react';
|
|
import { animateScroll } from 'react-scroll';
|
|
|
|
import { PAGINATION_FIELDS } from 'types/api/pagination';
|
|
import type { PaginationParams, PaginatedResponse, PaginatedQueryKeys, PaginationFilters } from 'types/api/pagination';
|
|
|
|
import useFetch from 'lib/hooks/useFetch';
|
|
|
|
interface Params<QueryName extends PaginatedQueryKeys> {
|
|
apiPath: string;
|
|
queryName: QueryName;
|
|
queryIds?: Array<string>;
|
|
filters?: PaginationFilters<QueryName>;
|
|
options?: Omit<UseQueryOptions<unknown, unknown, PaginatedResponse<QueryName>>, 'queryKey' | 'queryFn'>;
|
|
}
|
|
|
|
export default function useQueryWithPages<QueryName extends PaginatedQueryKeys>({ queryName, filters, options, apiPath, queryIds }: Params<QueryName>) {
|
|
const paginationFields = PAGINATION_FIELDS[queryName];
|
|
const queryClient = useQueryClient();
|
|
const router = useRouter();
|
|
const [ page, setPage ] = React.useState<number>(router.query.page && !Array.isArray(router.query.page) ? Number(router.query.page) : 1);
|
|
const currPageParams = pick(router.query, paginationFields);
|
|
const [ pageParams, setPageParams ] = React.useState<Array<PaginationParams<QueryName>>>([ ]);
|
|
const fetch = useFetch();
|
|
const isMounted = React.useRef(false);
|
|
const canGoBackwards = React.useRef(!router.query.page);
|
|
|
|
const queryKey = [ queryName, ...(queryIds || []), { page, filters } ];
|
|
|
|
const queryResult = useQuery<unknown, unknown, PaginatedResponse<QueryName>>(
|
|
queryKey,
|
|
async() => {
|
|
const params: Array<string> = [];
|
|
|
|
Object.entries({ ...filters, ...currPageParams }).forEach(([ key, val ]) => {
|
|
if (Array.isArray(val)) {
|
|
val.length && params.push(`${ key }=${ val.join(',') }`);
|
|
} else if (val) {
|
|
params.push(`${ key }=${ val }`);
|
|
}
|
|
});
|
|
|
|
return fetch(`${ apiPath }${ params.length ? '?' + params.join('&') : '' }`);
|
|
},
|
|
{ staleTime: page === 1 ? 0 : Infinity, ...options },
|
|
);
|
|
const { data } = queryResult;
|
|
|
|
const onNextPageClick = useCallback(() => {
|
|
if (!data?.next_page_params) {
|
|
// we hide next page button if no next_page_params
|
|
return;
|
|
}
|
|
const nextPageParams = data.next_page_params;
|
|
if (page >= pageParams.length && data?.next_page_params) {
|
|
setPageParams(prev => [ ...prev, nextPageParams ]);
|
|
}
|
|
const nextPageQuery = { ...router.query };
|
|
Object.entries(nextPageParams).forEach(([ key, val ]) => nextPageQuery[key] = val.toString());
|
|
nextPageQuery.page = String(page + 1);
|
|
|
|
router.push({ pathname: router.pathname, query: nextPageQuery }, undefined, { shallow: true })
|
|
.then(() => {
|
|
animateScroll.scrollToTop({ duration: 0 });
|
|
setPage(prev => prev + 1);
|
|
});
|
|
}, [ data?.next_page_params, page, pageParams.length, router ]);
|
|
|
|
const onPrevPageClick = useCallback(() => {
|
|
// returning to the first page
|
|
// we dont have pagination params for the first page
|
|
let nextPageQuery: typeof router.query = { ...router.query };
|
|
if (page === 2) {
|
|
nextPageQuery = omit(router.query, paginationFields, 'page');
|
|
canGoBackwards.current = true;
|
|
} else {
|
|
const nextPageParams = { ...pageParams[page - 2] };
|
|
nextPageParams && Object.entries(nextPageParams).forEach(([ key, val ]) => nextPageQuery[key] = val.toString());
|
|
nextPageQuery.page = String(page - 1);
|
|
}
|
|
router.query = nextPageQuery;
|
|
router.push({ pathname: router.pathname, query: nextPageQuery }, undefined, { shallow: true })
|
|
.then(() => {
|
|
animateScroll.scrollToTop({ duration: 0 });
|
|
setPage(prev => prev - 1);
|
|
page === 2 && queryClient.removeQueries({ queryKey: [ queryName ] });
|
|
});
|
|
}, [ router, page, paginationFields, pageParams, queryClient, queryName ]);
|
|
|
|
const resetPage = useCallback(() => {
|
|
queryClient.removeQueries({ queryKey: [ queryName ] });
|
|
|
|
router.push({ pathname: router.pathname, query: omit(router.query, paginationFields, 'page') }, undefined, { shallow: true }).then(() => {
|
|
animateScroll.scrollToTop({ duration: 0 });
|
|
setPage(1);
|
|
setPageParams([ ]);
|
|
canGoBackwards.current = true;
|
|
window.setTimeout(() => {
|
|
// FIXME after router is updated we still have inactive queries for previously visited page (e.g third), where we came from
|
|
// so have to remove it but with some delay :)
|
|
queryClient.removeQueries({ queryKey: [ queryName ], type: 'inactive' });
|
|
}, 100);
|
|
});
|
|
}, [ queryClient, queryName, router, paginationFields ]);
|
|
|
|
const hasPaginationParams = Object.keys(currPageParams).length > 0;
|
|
const nextPageParams = data?.next_page_params;
|
|
|
|
const pagination = {
|
|
page,
|
|
onNextPageClick,
|
|
onPrevPageClick,
|
|
hasPaginationParams,
|
|
resetPage,
|
|
hasNextPage: nextPageParams ? Object.keys(nextPageParams).length > 0 : false,
|
|
canGoBackwards: canGoBackwards.current,
|
|
};
|
|
|
|
React.useEffect(() => {
|
|
if (page !== 1 && isMounted.current) {
|
|
queryClient.cancelQueries({ queryKey });
|
|
setPage(1);
|
|
}
|
|
// hook should run only when queryName has changed
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [ queryName ]);
|
|
|
|
React.useEffect(() => {
|
|
window.setTimeout(() => {
|
|
isMounted.current = true;
|
|
}, 0);
|
|
}, []);
|
|
|
|
return { ...queryResult, pagination };
|
|
}
|