Commit d7b82bbe authored by tom's avatar tom

store all api pagination param in one query param

parent 22cbf9f4
......@@ -18,7 +18,8 @@ export default function buildUrl<R extends ResourceName>(
const url = new URL(compile(path)(pathParams), baseUrl);
queryParams && Object.entries(queryParams).forEach(([ key, value ]) => {
value && url.searchParams.append(key, String(value));
// there are some pagination params that can be null for the next page
(value || value === null) && url.searchParams.append(key, String(value));
});
return url.toString();
......
......@@ -160,7 +160,6 @@ export const RESOURCES = {
// BLOCKS, TXS
blocks: {
path: '/api/v2/blocks',
paginationFields: [ 'block_number' as const, 'items_count' as const ],
filterFields: [ 'type' as const ],
},
block: {
......@@ -170,28 +169,23 @@ export const RESOURCES = {
block_txs: {
path: '/api/v2/blocks/:height_or_hash/transactions',
pathParams: [ 'height_or_hash' as const ],
paginationFields: [ 'block_number' as const, 'items_count' as const, 'index' as const ],
filterFields: [],
},
block_withdrawals: {
path: '/api/v2/blocks/:height_or_hash/withdrawals',
pathParams: [ 'height_or_hash' as const ],
paginationFields: [ 'items_count' as const, 'index' as const ],
filterFields: [],
},
txs_validated: {
path: '/api/v2/transactions',
paginationFields: [ 'block_number' as const, 'items_count' as const, 'filter' as const, 'index' as const ],
filterFields: [ 'filter' as const, 'type' as const, 'method' as const ],
},
txs_pending: {
path: '/api/v2/transactions',
paginationFields: [ 'filter' as const, 'hash' as const, 'inserted_at' as const ],
filterFields: [ 'filter' as const, 'type' as const, 'method' as const ],
},
txs_watchlist: {
path: '/api/v2/transactions/watchlist',
paginationFields: [ 'block_number' as const, 'index' as const, 'items_count' as const ],
filterFields: [ ],
},
tx: {
......@@ -201,19 +195,16 @@ export const RESOURCES = {
tx_internal_txs: {
path: '/api/v2/transactions/:hash/internal-transactions',
pathParams: [ 'hash' as const ],
paginationFields: [ 'block_number' as const, 'items_count' as const, 'transaction_hash' as const, 'index' as const, 'transaction_index' as const ],
filterFields: [ ],
},
tx_logs: {
path: '/api/v2/transactions/:hash/logs',
pathParams: [ 'hash' as const ],
paginationFields: [ 'items_count' as const, 'transaction_hash' as const, 'index' as const ],
filterFields: [ ],
},
tx_token_transfers: {
path: '/api/v2/transactions/:hash/token-transfers',
pathParams: [ 'hash' as const ],
paginationFields: [ 'block_number' as const, 'items_count' as const, 'transaction_hash' as const, 'index' as const ],
filterFields: [ 'type' as const ],
},
tx_raw_trace: {
......@@ -226,7 +217,6 @@ export const RESOURCES = {
},
withdrawals: {
path: '/api/v2/withdrawals',
paginationFields: [ 'index' as const, 'items_count' as const ],
filterFields: [],
},
withdrawals_counters: {
......@@ -236,7 +226,6 @@ export const RESOURCES = {
// ADDRESSES
addresses: {
path: '/api/v2/addresses/',
paginationFields: [ 'fetched_coin_balance' as const, 'hash' as const, 'items_count' as const ],
filterFields: [ ],
},
......@@ -256,31 +245,26 @@ export const RESOURCES = {
address_txs: {
path: '/api/v2/addresses/:hash/transactions',
pathParams: [ 'hash' as const ],
paginationFields: [ 'block_number' as const, 'items_count' as const, 'index' as const ],
filterFields: [ 'filter' as const ],
},
address_internal_txs: {
path: '/api/v2/addresses/:hash/internal-transactions',
pathParams: [ 'hash' as const ],
paginationFields: [ 'block_number' as const, 'items_count' as const, 'index' as const, 'transaction_index' as const ],
filterFields: [ 'filter' as const ],
},
address_token_transfers: {
path: '/api/v2/addresses/:hash/token-transfers',
pathParams: [ 'hash' as const ],
paginationFields: [ 'block_number' as const, 'items_count' as const, 'index' as const, 'transaction_index' as const ],
filterFields: [ 'filter' as const, 'type' as const, 'token' as const ],
},
address_blocks_validated: {
path: '/api/v2/addresses/:hash/blocks-validated',
pathParams: [ 'hash' as const ],
paginationFields: [ 'items_count' as const, 'block_number' as const ],
filterFields: [ ],
},
address_coin_balance: {
path: '/api/v2/addresses/:hash/coin-balance-history',
pathParams: [ 'hash' as const ],
paginationFields: [ 'items_count' as const, 'block_number' as const ],
filterFields: [ ],
},
address_coin_balance_chart: {
......@@ -290,19 +274,16 @@ export const RESOURCES = {
address_logs: {
path: '/api/v2/addresses/:hash/logs',
pathParams: [ 'hash' as const ],
paginationFields: [ 'items_count' as const, 'transaction_index' as const, 'index' as const, 'block_number' as const ],
filterFields: [ ],
},
address_tokens: {
path: '/api/v2/addresses/:hash/tokens',
pathParams: [ 'hash' as const ],
paginationFields: [ 'items_count' as const, 'token_name' as const, 'token_type' as const, 'value' as const, 'fiat_value' as const, 'id' as const ],
filterFields: [ 'type' as const ],
},
address_withdrawals: {
path: '/api/v2/addresses/:hash/withdrawals',
pathParams: [ 'hash' as const ],
paginationFields: [ 'items_count' as const, 'index' as const ],
filterFields: [],
},
......@@ -341,7 +322,6 @@ export const RESOURCES = {
verified_contracts: {
path: '/api/v2/smart-contracts',
paginationFields: [ 'items_count' as const, 'smart_contract_id' as const ],
filterFields: [ 'q' as const, 'filter' as const ],
},
verified_contracts_counters: {
......@@ -366,24 +346,20 @@ export const RESOURCES = {
token_holders: {
path: '/api/v2/tokens/:hash/holders',
pathParams: [ 'hash' as const ],
paginationFields: [ 'items_count' as const, 'value' as const ],
filterFields: [],
},
token_transfers: {
path: '/api/v2/tokens/:hash/transfers',
pathParams: [ 'hash' as const ],
paginationFields: [ 'block_number' as const, 'items_count' as const, 'index' as const ],
filterFields: [],
},
token_inventory: {
path: '/api/v2/tokens/:hash/instances',
pathParams: [ 'hash' as const ],
paginationFields: [ 'unique_token' as const ],
filterFields: [],
},
tokens: {
path: '/api/v2/tokens',
paginationFields: [ 'holder_count' as const, 'items_count' as const, 'name' as const, 'market_cap' as const ],
filterFields: [ 'q' as const, 'type' as const ],
},
......@@ -399,13 +375,11 @@ export const RESOURCES = {
token_instance_transfers: {
path: '/api/v2/tokens/:hash/instances/:id/transfers',
pathParams: [ 'hash' as const, 'id' as const ],
paginationFields: [ 'block_number' as const, 'items_count' as const, 'index' as const, 'token_id' as const ],
filterFields: [],
},
token_instance_holders: {
path: '/api/v2/tokens/:hash/instances/:id/holders',
pathParams: [ 'hash' as const, 'id' as const ],
paginationFields: [ 'items_count' as const, 'token_id' as const, 'value' as const ],
filterFields: [],
},
......@@ -438,17 +412,6 @@ export const RESOURCES = {
// SEARCH
search: {
path: '/api/v2/search',
paginationFields: [
'address_hash' as const,
'block_hash' as const,
'holder_count' as const,
'inserted_at' as const,
'item_type' as const,
'items_count' as const,
'name' as const,
'q' as const,
'tx_hash' as const,
],
filterFields: [ 'q' ],
},
search_check_redirect: {
......@@ -463,7 +426,6 @@ export const RESOURCES = {
// L2
l2_deposits: {
path: '/api/v2/optimism/deposits',
paginationFields: [ 'nonce' as const, 'items_count' as const ],
filterFields: [],
},
......@@ -473,7 +435,6 @@ export const RESOURCES = {
l2_withdrawals: {
path: '/api/v2/optimism/withdrawals',
paginationFields: [ 'nonce' as const, 'items_count' as const ],
filterFields: [],
},
......@@ -483,7 +444,6 @@ export const RESOURCES = {
l2_output_roots: {
path: '/api/v2/optimism/output-roots',
paginationFields: [ 'index' as const, 'items_count' as const ],
filterFields: [],
},
......@@ -493,7 +453,6 @@ export const RESOURCES = {
l2_txn_batches: {
path: '/api/v2/optimism/txn-batches',
paginationFields: [ 'block_number' as const, 'items_count' as const ],
filterFields: [],
},
......@@ -527,10 +486,6 @@ export type ResourceFiltersKey<R extends ResourceName> = typeof RESOURCES[R] ext
ArrayElement<typeof RESOURCES[R]['filterFields']> :
never;
export type ResourcePaginationKey<R extends ResourceName> = typeof RESOURCES[R] extends {paginationFields: Array<unknown>} ?
ArrayElement<typeof RESOURCES[R]['paginationFields']> :
never;
export const resourceKey = (x: keyof typeof RESOURCES) => x;
type ResourcePathParamName<Resource extends ResourceName> =
......
import { useQueryClient } from '@tanstack/react-query';
import difference from 'lodash/difference';
import mapValues from 'lodash/mapValues';
import omit from 'lodash/omit';
import pick from 'lodash/pick';
import { useRouter } from 'next/router';
import React, { useCallback } from 'react';
import { animateScroll } from 'react-scroll';
import type { PaginatedResources, PaginatedResponse, PaginationFilters } from 'lib/api/resources';
import type { PaginatedResources, PaginationFilters } from 'lib/api/resources';
import { RESOURCES } from 'lib/api/resources';
import type { Params as UseApiQueryParams } from 'lib/api/useApiQuery';
import useApiQuery from 'lib/api/useApiQuery';
import getQueryParamString from 'lib/router/getQueryParamString';
interface Params<Resource extends PaginatedResources> {
resourceName: Resource;
......@@ -20,6 +18,18 @@ interface Params<Resource extends PaginatedResources> {
scrollRef?: React.RefObject<HTMLDivElement>;
}
type NextPageParams = Record<string, unknown>;
function getPaginationParamsFromQuery(queryString: string | Array<string> | undefined) {
if (queryString) {
try {
return JSON.parse(decodeURIComponent(getQueryParamString(queryString))) as NextPageParams;
} catch (error) {}
}
return {};
}
export default function useQueryWithPages<Resource extends PaginatedResources>({
resourceName,
filters,
......@@ -31,14 +41,9 @@ export default function useQueryWithPages<Resource extends PaginatedResources>({
const queryClient = useQueryClient();
const router = useRouter();
type NextPageParams = {
[K in keyof PaginatedResponse<Resource>['next_page_params']]: string;
}
const currPageParams = mapValues(pick(router.query, resource.paginationFields), (value) => value?.toString()) as NextPageParams;
const [ page, setPage ] = React.useState<number>(router.query.page && !Array.isArray(router.query.page) ? Number(router.query.page) : 1);
const [ pageParams, setPageParams ] = React.useState<Record<number, NextPageParams>>({
[page]: currPageParams,
[page]: getPaginationParamsFromQuery(router.query.next_page_params),
});
const [ hasPagination, setHasPagination ] = React.useState(page > 1);
......@@ -65,21 +70,21 @@ export default function useQueryWithPages<Resource extends PaginatedResources>({
// we hide next page button if no next_page_params
return;
}
const nextPageParams = data.next_page_params;
setPageParams((prev) => ({
...prev,
[page + 1]: mapValues(nextPageParams, (value) => String(value)) as NextPageParams,
[page + 1]: data.next_page_params as NextPageParams,
}));
setPage(prev => prev + 1);
const nextPageQuery = { ...router.query };
Object.entries(nextPageParams).forEach(([ key, val ]) => nextPageQuery[key] = String(val));
nextPageQuery.page = String(page + 1);
setHasPagination(true);
const nextPageQuery = {
...router.query,
page: String(page + 1),
next_page_params: encodeURIComponent(JSON.stringify(data.next_page_params)),
};
setHasPagination(true);
scrollToTop();
router.push({ pathname: router.pathname, query: nextPageQuery }, undefined, { shallow: true });
}, [ data?.next_page_params, page, router, scrollToTop ]);
......@@ -88,7 +93,7 @@ export default function useQueryWithPages<Resource extends PaginatedResources>({
// we dont have pagination params for the first page
let nextPageQuery: typeof router.query = { ...router.query };
if (page === 2) {
nextPageQuery = omit(router.query, difference(resource.paginationFields, resource.filterFields), 'page');
nextPageQuery = omit(router.query, [ 'next_page_params', 'page' ]);
canGoBackwards.current = true;
} else {
const nextPageParams = pageParams[page - 1];
......@@ -103,13 +108,13 @@ export default function useQueryWithPages<Resource extends PaginatedResources>({
page === 2 && queryClient.removeQueries({ queryKey: [ resourceName ] });
});
setHasPagination(true);
}, [ router, page, resource.paginationFields, resource.filterFields, pageParams, scrollToTop, queryClient, resourceName ]);
}, [ router, page, pageParams, scrollToTop, queryClient, resourceName ]);
const resetPage = useCallback(() => {
queryClient.removeQueries({ queryKey: [ resourceName ] });
scrollToTop();
const nextRouterQuery = omit(router.query, difference(resource.paginationFields, resource.filterFields), 'page');
const nextRouterQuery = omit(router.query, [ 'next_page_params', 'page' ]);
router.push({ pathname: router.pathname, query: nextRouterQuery }, undefined, { shallow: true }).then(() => {
queryClient.removeQueries({ queryKey: [ resourceName ] });
setPage(1);
......@@ -123,10 +128,10 @@ export default function useQueryWithPages<Resource extends PaginatedResources>({
});
setHasPagination(true);
}, [ queryClient, resourceName, router, resource.paginationFields, resource.filterFields, scrollToTop ]);
}, [ queryClient, resourceName, router, scrollToTop ]);
const onFilterChange = useCallback((newFilters: PaginationFilters<Resource> | undefined) => {
const newQuery = omit<typeof router.query>(router.query, resource.paginationFields, 'page', resource.filterFields);
const newQuery = omit<typeof router.query>(router.query, 'next_page_params', 'page', resource.filterFields);
if (newFilters) {
Object.entries(newFilters).forEach(([ key, value ]) => {
if (value && value.length) {
......@@ -147,7 +152,7 @@ export default function useQueryWithPages<Resource extends PaginatedResources>({
setPage(1);
setPageParams({});
});
}, [ router, resource.paginationFields, resource.filterFields, scrollToTop ]);
}, [ router, resource.filterFields, scrollToTop ]);
const nextPageParams = data?.next_page_params;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment