Skip to content

rjdellecese/convex-use-next-prev-paginated-query

Repository files navigation

useNextPrevPaginatedQuery

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.

Installation

npm install convex-use-next-prev-paginated-query
pnpm add convex-use-next-prev-paginated-query
yarn add convex-use-next-prev-paginated-query

Usage

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";
  }
};