Infinite Queries

Infinite queries

Infinite queries are query call that keep fetching data until the user stops the query. Infinite queries are useful for paginated data.

To handle this, tRPC-SWR provides the createSWRInfiniteProxy function. It creates a proxy that leverages SWR's useSWRInfinite hook.

Setup

In your trpc utils file, import the infinite library and pass it the current trpc client

import { createSWRInfiniteProxy } from "@trpc-swr/infinite";
 
// ... previous tRPC hooks above
 
export const infinite = createSWRInfiniteProxy(client);

Usage

Using limit & page

In your paginated route:

const userRouter = t.router({
  getMany: t.procedure
    .input({
      limit: t.number.optional.default(10),
      page: t.number.optional.default(1),
    })
    .query(({ input }) => {
      return prisma.user.findMany({
        take: input.limit,
        skip: input.limit * (input.page - 1),
      });
    }),
});

In your React component:

const { data, error, size, setSize } = infinite.user.getMany.use(
  (index, previousPageData) => {
    return {
      limit: 10,
      page: 1,
    };
  }
);
 
const users = data?.pages.flat() || [];
 
return (
  <div>
    {users.map((user) => (
      <div key={user.id}>{user.name}</div>
    ))}
    <button
      onClick={() => {
        setSize(size + 1);
      }}
    >
      Load more
    </button>
  </div>
);

Using cursor

In your paginated route:

Example with prisma

const userRouter = t.router({
  getMany: t.procedure
    .input({
      limit: z.number().min(1).max(100).nullish(),
      cursor: z.number().nullish(),
    })
    .query(({ input }) => {
      const limit = input.limit ?? 10;
      const { cursor } = input;
 
      const items = await prisma.user.findMany({
        take: limit + 1,
        cursor: cursor ? { myCursor: cursor } : undefined,
        orderBy: { myCursor: "asc" },
      });
 
      let nextCursor: typeof cursor | null = null;
      if (items.length > limit) {
        nextCursor = items[items.length - 1].myCursor;
        items.pop();
      }
 
      return {
        items,
        nextCursor,
      };
    }),
});

In your React component:

The useCursor method is a convenience wrapper around use. It will automatically pass the nextCursor as the cursor input to the next query. You can build this functionality with .use()

const { data, error, size, setSize } = infinite.user.getMany.useCursor(
  { limit: 3 },
  (index, previousPageData) => {
    return previousPageData?.nextCursor;
  }
);
 
const users = data?.pages.flat() || [];
const hasMore = data?.at(-1).nextCursor !== null;
 
return (
  <div>
    {users.map((user) => (
      <div key={user.id}>{user.name}</div>
    ))}
    {hasMore && (
      <button
        onClick={() => {
          setSize(size + 1);
        }}
      >
        Load more
      </button>
    )}
  </div>
);