73 lines
2.1 KiB
TypeScript

/** Cursor pagination state (previous-page stack) shared by all paged tables. */
import { useCallback, useEffect, useMemo, useState } from "react";
export interface CursorPager {
/** Cursor to pass to the backend for the current page (null = first page). */
cursor: string | null;
/** 1-based page number. */
page: number;
canPrev: boolean;
goNext: (nextCursor: string | null) => void;
goPrev: () => void;
reset: () => void;
}
export interface PagerState {
cursor: string | null;
/** Stack of cursors that produced the previous pages (page 1 = null). */
stack: (string | null)[];
}
export const INITIAL_PAGER_STATE: PagerState = { cursor: null, stack: [] };
/** Advance to the page identified by `nextCursor`; unchanged when null. */
export function advancePager(state: PagerState, nextCursor: string | null): PagerState {
if (!nextCursor) return state;
return { cursor: nextCursor, stack: [...state.stack, state.cursor] };
}
/** Go back one page; unchanged on the first page. */
export function retreatPager(state: PagerState): PagerState {
if (state.stack.length === 0) return state;
const stack = [...state.stack];
const cursor = stack.pop() ?? null;
return { cursor, stack };
}
export function pageNumber(state: PagerState): number {
return state.stack.length + 1;
}
/**
* Cursor pager that resets whenever `resetKey` changes (run id, filters…).
*/
export function useCursorPager(resetKey: string): CursorPager {
const [state, setState] = useState<PagerState>(INITIAL_PAGER_STATE);
useEffect(() => {
setState(INITIAL_PAGER_STATE);
}, [resetKey]);
const goNext = useCallback((nextCursor: string | null) => {
setState((s) => advancePager(s, nextCursor));
}, []);
const goPrev = useCallback(() => {
setState((s) => retreatPager(s));
}, []);
const reset = useCallback(() => setState(INITIAL_PAGER_STATE), []);
return useMemo(
() => ({
cursor: state.cursor,
page: pageNumber(state),
canPrev: state.stack.length > 0,
goNext,
goPrev,
reset,
}),
[state, goNext, goPrev, reset],
);
}