feat(pagination): add pagination state management and cursor handling
This commit is contained in:
@@ -28,6 +28,7 @@ export const ResolveSpecAdapter = forwardRef<AdapterRef, AdapterConfig>(
|
||||
const sorting = useGriddyStore((s) => s.sorting ?? []);
|
||||
const columnFilters = useGriddyStore((s) => s.columnFilters ?? []);
|
||||
const pagination = useGriddyStore((s) => s.pagination);
|
||||
const paginationState = useGriddyStore((s) => s.paginationState);
|
||||
const setData = useGriddyStore((s) => s.setData);
|
||||
const appendData = useGriddyStore((s) => s.appendData);
|
||||
const setDataCount = useGriddyStore((s) => s.setDataCount);
|
||||
@@ -39,12 +40,11 @@ export const ResolveSpecAdapter = forwardRef<AdapterRef, AdapterConfig>(
|
||||
const debounceRef = useRef<null | ReturnType<typeof setTimeout>>(null);
|
||||
const mountedRef = useRef(true);
|
||||
|
||||
// Cursor state (only used in cursor mode)
|
||||
// Infinite scroll state (cursor mode)
|
||||
const cursorRef = useRef<null | string>(null);
|
||||
const hasMoreRef = useRef(true);
|
||||
const [cursorLoading, setCursorLoading] = useState(false);
|
||||
|
||||
// Update client if baseUrl/token changes
|
||||
useEffect(() => {
|
||||
clientRef.current = getResolveSpecClient({ baseUrl, token });
|
||||
}, [baseUrl, token]);
|
||||
@@ -64,14 +64,14 @@ export const ResolveSpecAdapter = forwardRef<AdapterRef, AdapterConfig>(
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const paginationState = pagination?.enabled
|
||||
? { pageIndex: 0, pageSize: pagination.pageSize }
|
||||
: undefined;
|
||||
// Fall back to config when store hasn't synced yet (initial render)
|
||||
const effectivePagination = paginationState ??
|
||||
(pagination?.enabled ? { pageIndex: 0, pageSize: pagination.pageSize } : undefined);
|
||||
|
||||
const options = buildOptions(
|
||||
sorting,
|
||||
columnFilters,
|
||||
paginationState,
|
||||
effectivePagination,
|
||||
columnMap,
|
||||
defaultOptions
|
||||
);
|
||||
@@ -85,7 +85,8 @@ export const ResolveSpecAdapter = forwardRef<AdapterRef, AdapterConfig>(
|
||||
if (!mountedRef.current) return;
|
||||
|
||||
if (response.success) {
|
||||
setData(Array.isArray(response.data) ? response.data : [response.data]);
|
||||
const rows = Array.isArray(response.data) ? response.data : [response.data];
|
||||
setData(rows);
|
||||
if (response.metadata?.total != null) {
|
||||
setDataCount(response.metadata.total);
|
||||
}
|
||||
@@ -105,6 +106,7 @@ export const ResolveSpecAdapter = forwardRef<AdapterRef, AdapterConfig>(
|
||||
sorting,
|
||||
columnFilters,
|
||||
pagination,
|
||||
paginationState,
|
||||
columnMap,
|
||||
defaultOptions,
|
||||
preload,
|
||||
@@ -118,7 +120,7 @@ export const ResolveSpecAdapter = forwardRef<AdapterRef, AdapterConfig>(
|
||||
setError,
|
||||
]);
|
||||
|
||||
// ─── Cursor mode fetch ───
|
||||
// ─── Cursor mode fetch (uses cursor_forward only) ───
|
||||
const fetchCursorPage = useCallback(
|
||||
async (cursor: null | string, isAppend: boolean) => {
|
||||
if (!mountedRef.current) return;
|
||||
@@ -143,7 +145,7 @@ export const ResolveSpecAdapter = forwardRef<AdapterRef, AdapterConfig>(
|
||||
if (computedColumns) options.computedColumns = computedColumns;
|
||||
if (customOperators) options.customOperators = customOperators;
|
||||
|
||||
const cursorOptions = applyCursor(options, cursor, pageSize);
|
||||
const cursorOptions = applyCursor(options, pageSize, cursor);
|
||||
const response = await clientRef.current.read(schema, entity, undefined, cursorOptions);
|
||||
|
||||
if (!mountedRef.current) return;
|
||||
@@ -157,18 +159,26 @@ export const ResolveSpecAdapter = forwardRef<AdapterRef, AdapterConfig>(
|
||||
setData(rows);
|
||||
}
|
||||
|
||||
if (response.metadata?.total != null) {
|
||||
if (response.metadata?.total) {
|
||||
setDataCount(response.metadata.total);
|
||||
} else if (response.metadata?.count) {
|
||||
setDataCount(response.metadata.count);
|
||||
}
|
||||
|
||||
// Extract cursor from last row
|
||||
if (rows.length > 0) {
|
||||
const lastRow = rows[rows.length - 1];
|
||||
cursorRef.current =
|
||||
lastRow?.[cursorField] != null ? String(lastRow[cursorField]) : null;
|
||||
if (lastRow?.[cursorField] == null) {
|
||||
hasMoreRef.current = false;
|
||||
setError(new Error(
|
||||
`Cursor field "${cursorField}" not found in response data. ` +
|
||||
`Set cursorField to match your data's primary key, or use mode="offset".`
|
||||
));
|
||||
return;
|
||||
}
|
||||
cursorRef.current = String(lastRow[cursorField]);
|
||||
}
|
||||
|
||||
// Determine hasMore
|
||||
hasMoreRef.current = rows.length >= pageSize;
|
||||
} else if (response.error) {
|
||||
setError(new Error(response.error.message ?? 'Request failed'));
|
||||
@@ -209,7 +219,7 @@ export const ResolveSpecAdapter = forwardRef<AdapterRef, AdapterConfig>(
|
||||
|
||||
const fetchNextPage = useCallback(() => {
|
||||
if (!hasMoreRef.current || cursorLoading) return;
|
||||
fetchCursorPage(cursorRef.current, true);
|
||||
return fetchCursorPage(cursorRef.current, true);
|
||||
}, [cursorLoading, fetchCursorPage]);
|
||||
|
||||
const resetAndFetch = useCallback(async () => {
|
||||
@@ -223,7 +233,6 @@ export const ResolveSpecAdapter = forwardRef<AdapterRef, AdapterConfig>(
|
||||
|
||||
// ─── Infinite scroll config sync (cursor mode only) ───
|
||||
useEffect(() => {
|
||||
// Skip infinite scroll if not in cursor mode OR if pagination is explicitly enabled
|
||||
if (mode !== 'cursor' || pagination?.enabled) {
|
||||
setInfiniteScroll(undefined);
|
||||
return;
|
||||
@@ -238,7 +247,6 @@ export const ResolveSpecAdapter = forwardRef<AdapterRef, AdapterConfig>(
|
||||
});
|
||||
}, [mode, pagination?.enabled, cursorLoading, fetchNextPage, setInfiniteScroll]);
|
||||
|
||||
// Cleanup infinite scroll on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
setInfiniteScroll(undefined);
|
||||
@@ -247,7 +255,6 @@ export const ResolveSpecAdapter = forwardRef<AdapterRef, AdapterConfig>(
|
||||
|
||||
useImperativeHandle(ref, () => ({ refetch: fetchData }), [fetchData]);
|
||||
|
||||
// Auto-fetch on mount
|
||||
const initialFetchDone = useRef(false);
|
||||
useEffect(() => {
|
||||
if (autoFetch && !initialFetchDone.current) {
|
||||
@@ -256,13 +263,12 @@ export const ResolveSpecAdapter = forwardRef<AdapterRef, AdapterConfig>(
|
||||
}
|
||||
}, [autoFetch, fetchData]);
|
||||
|
||||
// Debounced re-fetch on state changes (skip initial)
|
||||
const prevDepsRef = useRef<null | string>(null);
|
||||
useEffect(() => {
|
||||
const depsKey =
|
||||
mode === 'cursor'
|
||||
? JSON.stringify({ columnFilters, sorting })
|
||||
: JSON.stringify({ columnFilters, pagination, sorting });
|
||||
: JSON.stringify({ columnFilters, paginationState, sorting });
|
||||
|
||||
if (prevDepsRef.current === null) {
|
||||
prevDepsRef.current = depsKey;
|
||||
@@ -278,7 +284,7 @@ export const ResolveSpecAdapter = forwardRef<AdapterRef, AdapterConfig>(
|
||||
return () => {
|
||||
if (debounceRef.current) clearTimeout(debounceRef.current);
|
||||
};
|
||||
}, [sorting, columnFilters, pagination, debounceMs, fetchData, mode]);
|
||||
}, [sorting, columnFilters, paginationState, debounceMs, fetchData, mode]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user