#preloadQuery and pagination query

10 messages · Page 1 of 1 (latest)

jagged bough
#

what is the recommend way to preload a paginated query on the server with nextjs and convex?

ivory robinBOT
#

Hi @jagged bough. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

Based on the knowledge sources, there's currently a limitation with preloading paginated queries in Next.js with Convex. According to a Discord support thread, you cannot directly preload a paginated query because of how pagination cursors work in Convex.

The issue is that pagination cursors in Convex are session-specific. When you preload data on the server, the request is sent by the Convex client on the server (e.g., Vercel's servers). When you try to hydrate that data on the client, the client attempts to use the same cursor with a different session ID, resulting in an error like "Failed to parse cursor" Discord support thread.

The recommended workaround is to:

  1. Create a regular, non-paginated query that fetches the same initial chunk of data you would want to display first
  2. Use this non-paginated query with preloadQuery for server rendering
  3. Then use the paginated query on the client side after hydration

This approach means maintaining two separate queries that fetch similar data, which is admittedly inconvenient but currently necessary.

A community member in the Discord thread mentioned:

"Server side rendering a page that uses a paginated query is possible, convex just doesn't give you the sugar. [...] Try a nice skeleton loader instead, they work wonders."

The Convex team has indicated that TanStack-style SSR is more aligned with their approach rather than traditional Next.js server rendering for paginated queries.

#

If you're looking for alternatives, you might consider using skeleton loaders while the paginated data loads on the client side, which can provide a good user experience without the complexity of trying to preload paginated queries.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@jagged bough Kapa.ai is still learning and improving, please let me know how I did by reacting below.

jagged bough
#

@ivory robin What about using query params, useOptimistic and useTransition for filtering a query?

ivory robinBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

I understand you're asking about using query parameters, optimistic updates, and transitions for filtering queries in Convex. Let me address these aspects based on the knowledge sources.

Query Parameters and Dynamic Filtering

Convex supports building dynamic queries where you can conditionally apply filters based on query parameters. However, there are some TypeScript challenges when building these queries dynamically.

The traditional approach of building a query with conditional filters like this:

let query = ctx.db.query("messages");
if (args.authorFilter !== undefined) {
  query = query.withIndex("by_author", q=>q.eq("author", args.authorFilter));
}
// more conditional filters...

doesn't work well with TypeScript because the query's type changes after each operation Stack: Dynamic Query Builders.

Instead, you can use the filter helper from convex-helpers/server/filter which provides more powerful filtering capabilities:

import { filter } from "convex-helpers/server/filter";

filter(ctx.db.query("posts"),
  (post) => ...unlimited functionality...
).paginate(opts);

This approach allows for more complex filtering logic while maintaining type safety Stack: Complex Filters in Convex.

Optimistic Updates

For optimistic updates, Convex provides the .withOptimisticUpdate method that you can use with mutations to immediately update the UI before the server confirms the change:

#
const createNote = useMutation(api.notes.createNote).withOptimisticUpdate(
  (localStore, args) => {
    const existingNotes =
      localStore.getQuery(api.notes.getNotes, { boardId: actualBoardId }) || [];
    const tempId = `temp_${Date.now()}` as Id<"notes">;
    const now = Date.now();
    localStore.setQuery(api.notes.getNotes, { boardId: actualBoardId }, [
      ...existingNotes,
      { _id: tempId, _creationTime: now, ...args },
    ]);
  }
);

This helps create a more responsive user experience by showing changes immediately while the actual mutation is processing Stack: Keeping Users in Sync.

The OptimisticLocalStore interface provides methods like getQuery and getAllQueries to access the current state of queries in the client, which you can use to update the UI optimistically Convex Docs: OptimisticLocalStore.

Pagination with Filters

For pagination with filters, you can use the pagination feature with the filter helper:

export const postsWithTag = query({
  args: { tag: v.string(), paginationOpts: paginationOptsValidator },
  handler: (ctx, args) => {
    return filter(
      ctx.db.query("posts"),
      (post) => post.tags.includes(args.tag),
    ).paginate(args.paginationOpts);
  },
});

This approach allows you to apply complex filters while still supporting pagination Stack: Complex Filters in Convex.

While the knowledge sources don't specifically mention useTransition (which is a React feature), the combination of Convex's reactive queries and optimistic updates should provide a smooth user experience when filtering data.

#

For a more comprehensive solution to dynamic query building, you might want to check out community efforts like the one mentioned in the Discord thread where someone created a helper function for building queries dynamically Discord: responsive query and filter building.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: