Skip to content

Pagination

The library supports paginated document loading out of the box. Your DocumentSource.fetch returns a PageResult that tells the library how to fetch the next or previous page.

Two strategies are supported:

Cursor-based — Your API returns an opaque cursor string. The library passes it back to fetch the next page. Best for real-time data where rows can be inserted/deleted between requests.

// DocumentSource.fetch returns:
{
data: documents,
totalCount: 150,
next: { cursor: "eyJpZCI6MTAwfQ==" }, // opaque cursor
previous: { cursor: "eyJpZCI6NTB9" },
}

Offset-based — Classic skip/limit. The library sends an offset and limit to your API.

// DocumentSource.fetch returns:
{
data: documents,
totalCount: 150,
next: { offset: 50, limit: 25 },
previous: { offset: 0, limit: 25 },
}

The library doesn’t care which strategy you use — it stores whatever pagination token your source returns and passes it back on the next fetch.

Set pageSize in your initialize function:

const state = useCollectionManager({
collectionId: "my-collection",
user: "user-1",
initialize: async () => ({
collectionSource,
documentSource,
pageSize: 25, // rows per page
}),
});

Once you have a CollectionManager, use these methods to navigate pages:

const manager = state.manager;
const viewId = manager.views[0].id;
// Check if more pages are available
manager.hasNextPage(viewId); // boolean
manager.hasPreviousPage(viewId); // boolean
// Navigate
await manager.fetchNextPage(viewId);
await manager.fetchPreviousPage(viewId);
await manager.goToPage(viewId, 3); // jump to page 3 (offset-based only)
// Read pagination state
manager.getTotalCount(viewId); // number | undefined
manager.getPaginationStatus(viewId); // "fetching" | "idle"
manager.getPaginationState(viewId); // full state object
interface PaginationState {
results: string[]; // document IDs on the current page
totalCount: number | undefined; // total rows (if the source provides it)
currentPagination: PaginationStrategy | undefined;
nextPagination: PaginationStrategy | undefined;
previousPagination: PaginationStrategy | undefined;
}

Full Example: Paginated Table with Controls

Section titled “Full Example: Paginated Table with Controls”
import {
useCollectionManager,
useCollectionTableView,
defaultPropertyDefinitions,
TableView,
} from "@blocknote/block-view/react";
import { RestCollectionSource, RestDocumentSource } from "@blocknote/block-view/core/sources/rest";
import type { CollectionManager } from "@blocknote/block-view/core";
import "@blocknote/block-view/react/styles.css";
import "@blocknote/block-view/react/adapters/styles.css";
import { useState, useEffect } from "react";
function PaginatedTable({ manager, viewId }: { manager: CollectionManager; viewId: string }) {
const { table } = useCollectionTableView({
collectionManager: manager,
viewId,
propertyDefinitions: defaultPropertyDefinitions,
});
// Force re-render when pagination state changes
const [, forceUpdate] = useState(0);
useEffect(() => {
manager.addHooks({
documentsHydrated: () => forceUpdate((n) => n + 1),
});
}, [manager]);
const totalCount = manager.getTotalCount(viewId);
const hasNext = manager.hasNextPage(viewId);
const hasPrev = manager.hasPreviousPage(viewId);
const isFetching = manager.getPaginationStatus(viewId) === "fetching";
return (
<div>
<TableView table={table} editable />
<div style={{ display: "flex", gap: 8, marginTop: 8 }}>
<button onClick={() => manager.fetchPreviousPage(viewId)} disabled={!hasPrev || isFetching}>
Previous
</button>
<button onClick={() => manager.fetchNextPage(viewId)} disabled={!hasNext || isFetching}>
Next
</button>
{totalCount !== undefined && <span>Total: {totalCount} rows</span>}
{isFetching && <span>Loading...</span>}
</div>
</div>
);
}
export default function App() {
const state = useCollectionManager({
collectionId: "my-collection",
user: "user-1",
initialize: async () => ({
collectionSource: new RestCollectionSource({ baseUrl: "/api" }),
documentSource: new RestDocumentSource({ baseUrl: "/api" }),
pageSize: 25,
}),
});
if (state.status === "loading") return <p>Loading...</p>;
if (state.status === "error") return <p>Error: {state.error.message}</p>;
return <PaginatedTable manager={state.manager} viewId={state.manager.views[0].id} />;
}

Force a re-fetch of the current page:

await manager.refreshView(viewId);

This discards the current document set and re-fetches from the source, preserving the current pagination position.