A React hook for paginating through a Convex paginated query result one page at a time. Works with the same query functions as Convex's usePaginatedQuery
hook.
This hook keeps track of previous cursors in order to allow navigating forward and backwards through pages. It doesn't (yet) account for split pages.
npm install convex-use-next-prev-paginated-query
pnpm add convex-use-next-prev-paginated-query
yarn add convex-use-next-prev-paginated-query
Use this hook with a public query that accepts a paginationOpts
argument of type PaginationOptions
and returns a PaginationResult
, just like how the default usePaginatedQuery
works. See the this page of the Convex docs for more information on how to write a well-formed paginated query function. It might look something like this:
import { v } from "convex/values";
import { query, mutation } from "./_generated/server";
import { paginationOptsValidator } from "convex/server";
export const list = query({
args: { paginationOpts: paginationOptsValidator, channel: v.string() },
handler: async (ctx, args) =>
await ctx.db
.query("messages")
.withIndex("by_channel", (q) => q.eq("channel", args.channel))
.order("desc")
.paginate(args.paginationOpts),
});
Once you've defined your paginated query function, you can use it with this hook like so:
import { useNextPrevPaginatedQuery } from "convex-use-next-prev-paginated-query";
import { api } from "./_generated/api";
const MyComponent = () => {
const result = useNextPrevPaginatedQuery(
api.list,
{ channel: "general" },
{ initialNumItems: 10 }
);
if (result._tag === "Skipped") {
return <div>Skipped</div>;
} else if (result._tag === "LoadingInitialResults") {
return <div>LoadingInitialResults</div>;
} else if (result._tag === "Loaded") {
return (
<div>
<div>
{result.page.map((message) => (
<div key={message._id}>{message.text}</div>
))}
</div>
{result.loadNext && <button onClick={result.loadNext}>Next</button>}
Page {result.pageNum}
{result.loadPrev && <button onClick={result.loadPrev}>Prev</button>}
</div>
);
} else if (result._tag === "LoadingNextResults") {
return <div>LoadingNextResults</div>;
} else if (result._tag === "LoadingPrevResults") {
return <div>LoadingPrevResults</div>;
} else {
throw "Unknown state";
}
};