Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

store all api pagination param in one query param #844

Merged
merged 1 commit into from
May 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/api/buildUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
45 changes: 0 additions & 45 deletions lib/api/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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: {
Expand All @@ -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: {
Expand All @@ -226,7 +217,6 @@ export const RESOURCES = {
},
withdrawals: {
path: '/api/v2/withdrawals',
paginationFields: [ 'index' as const, 'items_count' as const ],
filterFields: [],
},
withdrawals_counters: {
Expand All @@ -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: [ ],
},

Expand All @@ -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: {
Expand All @@ -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: [],
},

Expand Down Expand Up @@ -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: {
Expand All @@ -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 ],
},

Expand All @@ -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: [],
},

Expand Down Expand Up @@ -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: {
Expand All @@ -463,7 +426,6 @@ export const RESOURCES = {
// L2
l2_deposits: {
path: '/api/v2/optimism/deposits',
paginationFields: [ 'nonce' as const, 'items_count' as const ],
filterFields: [],
},

Expand All @@ -473,7 +435,6 @@ export const RESOURCES = {

l2_withdrawals: {
path: '/api/v2/optimism/withdrawals',
paginationFields: [ 'nonce' as const, 'items_count' as const ],
filterFields: [],
},

Expand All @@ -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: [],
},

Expand All @@ -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: [],
},

Expand Down Expand Up @@ -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> =
Expand Down
51 changes: 28 additions & 23 deletions lib/hooks/useQueryWithPages.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
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;
Expand All @@ -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,
Expand All @@ -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);

Expand All @@ -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 ]);

Expand All @@ -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];
Expand All @@ -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);
Expand All @@ -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) {
Expand All @@ -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;

Expand Down