All files / packages/app/hooks use-cursor-paging.ts

86.95% Statements 20/23
62.5% Branches 5/8
100% Functions 5/5
86.95% Lines 20/23

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80                        81x                 149x 149x     149x     149x     81x                         1578x 1578x 1578x 1578x           1578x 1578x   145x 145x         1578x           4x 4x 4x 4x 4x              
import React from "react";
 
import type { SkipToken } from "@tanstack/react-query";
import { useSuspenseQuery } from "@tanstack/react-query";
 
import type {
	TRPCDecoratedInfiniteQueryProcedure,
	TRPCInfiniteQueryKey,
} from "~app/trpc";
import type { SearchParamState } from "~app/utils/navigation";
import { updateSetStateAction } from "~utils/react";
 
const validateOffset = ({
	limit,
	offset,
	count,
}: {
	limit: number;
	offset: number;
	count: number;
}) => {
	const maxOffset = count === 0 ? 0 : (Math.ceil(count / limit) - 1) * limit;
	Iif (offset > maxOffset) {
		return maxOffset;
	}
	Iif (offset % limit !== 0) {
		return Math.floor(offset / limit) * limit;
	}
	return offset;
};
 
export const useCursorPaging = <
	K extends TRPCInfiniteQueryKey,
	P extends TRPCDecoratedInfiniteQueryProcedure<K> =
		TRPCDecoratedInfiniteQueryProcedure<K>,
	I extends Exclude<Parameters<P["queryOptions"]>[0], SkipToken> = Exclude<
		Parameters<P["queryOptions"]>[0],
		SkipToken
	>,
>(
	procedure: P,
	input: Omit<I, "cursor">,
	offsetState: SearchParamState<"/_protected/receipts/", "offset">,
) => {
	const { limit } = input;
	const [offset, setOffset] = offsetState;
	const deferredOffset = React.useDeferredValue(offset);
	const { data } = useSuspenseQuery(
		// This is hacky, but I couldn't manage types here
		(
			procedure as TRPCDecoratedInfiniteQueryProcedure<"peers.getPaged">
		).queryOptions({ ...input, cursor: deferredOffset }),
	);
	const { count } = data;
	React.useEffect(() => {
		// This affect helps with invalid navigations / limit changes
		const validatedOffset = validateOffset({ offset, limit, count });
		Iif (validatedOffset !== offset) {
			setOffset(validatedOffset);
		}
	}, [count, limit, offset, setOffset]);
 
	return {
		data: data as P["~types"]["output"],
		onPageChange: React.useCallback<
			React.Dispatch<React.SetStateAction<number>>
		>(
			(setStateAction: React.SetStateAction<number>) =>
				setOffset((prevOffset) => {
					const prevPage = Math.floor(prevOffset / limit) + 1;
					const nextPage = updateSetStateAction(setStateAction, prevPage);
					const nextOffset = (nextPage - 1) * limit;
					return validateOffset({ offset: nextOffset, limit, count });
				}),
			[setOffset, limit, count],
		),
		isPending: deferredOffset !== offset,
	};
};