#usePaginatedQuery return empty result at first load

15 messages · Page 1 of 1 (latest)

meager rapids
#

Here is my function:

export const list = authQuery({
  args: { search, sort, paginationOpts },
  handler: (ctx, args) => {
    return filter(
      ctx.db.query("users"),
      (user) =>
        user.name.toLowerCase().includes(args.search) ||
        user.email.toLowerCase().includes(args.search)
    ).paginate(args.paginationOpts)
  },
})

Here is how I used:

const { results, status, loadMore } = usePaginatedQuery(
    api.users.queries.list,
    {
      search: "user",
    },
    {
      initialNumItems: 10,
    }
  )

The result will return empty array at first, only will appear the result after clicking the load more button

sudden emberBOT
#

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

#

I understand you're experiencing an issue with usePaginatedQuery where it's returning an empty array initially, and only showing results after clicking the "load more" button.

The issue you're encountering is likely related to how the filter helper from convex-helpers works with pagination. When using the filter helper with pagination, it applies the filter to the paginated results after collecting the data, which can lead to empty or smaller pages than expected.

As explained in the Convex documentation:

TypeScript filters can work with pagination. This can return small or empty pages to the client, which can slow down load times and cost database bandwidth as it traverses the entire table across many pages. Stack Convex

A community member experienced a similar issue where they needed to use a larger initialNumItems value to get results:

The last query will return data only if I give a bigger value to initialNumItems: with value 3 it returns 2 records, but with 10 then it find the 3 records I'm expecting. Discord support thread

To fix this issue, you have a few options:

  1. Increase your initialNumItems value to ensure it captures enough data after filtering.

  2. If possible, use database indexes and built-in query filters instead of the filter helper:

    return ctx.db
      .query("users")
      .filter((q) => 
        q.or(
          q.where(q.field("name").lower(), q.includes(args.search.toLowerCase())),
          q.where(q.field("email").lower(), q.includes(args.search.toLowerCase()))
        )
      )
      .paginate(args.paginationOpts);
    
#
  1. Create a custom stable pagination hook that doesn't show loading states, similar to what's described in Stack Convex.

The key insight is that when using the filter helper with pagination, the filtering happens after the pagination, which can result in fewer items than requested or even empty pages if none of the paginated items match your filter.

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

#

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

meager rapids
#

@sudden ember using the ctx.db.query, how can I search for multiple fields?

sudden emberBOT
#

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

#

Based on your previous question about pagination with filtering, I understand you're now looking for ways to search across multiple fields using Convex's ctx.db.query. There are several approaches to this in Convex:

Using Filter with OR Conditions

You can use the filter method with q.or to search across multiple fields:

const results = await ctx.db
  .query("users")
  .filter((q) => 
    q.or(
      q.eq(q.field("name"), searchTerm),
      q.eq(q.field("email"), searchTerm)
    )
  )
  .collect();

For partial matches, you can use string operations:

const results = await ctx.db
  .query("users")
  .filter((q) => 
    q.or(
      q.field("name").includes(searchTerm),
      q.field("email").includes(searchTerm)
    )
  )
  .collect();

Using Full Text Search

For more powerful text searching, you can use Convex's full-text search capabilities:

  1. First, define a search index in your schema:
export default defineSchema({
  users: defineTable({
    name: v.string(),
    email: v.string(),
    // other fields...
  }).searchIndex("search_name", {
    searchField: "name",
  }).searchIndex("search_email", {
    searchField: "email",
  }),
});
  1. Then query using these search indexes:
// Search in name field
const nameResults = await ctx.db
  .query("users")
  .withSearchIndex("search_name", (q) => q.search("name", searchTerm))
  .collect();

// Search in email field
const emailResults = await ctx.db
  .query("users")
  .withSearchIndex("search_email", (q) => q.search("email", searchTerm))
  .collect();

Note that you can't directly combine multiple search indexes in a single query. You would need to run separate queries and merge the results in your code.

Using a Calculated Field

Another approach is to create a calculated field that combines multiple fields:

#
  1. Use a trigger to create a combined field:
export const generateSearchField = internalMutation({
  handler: async (ctx) => {
    const users = await ctx.db.query("users").collect();
    for (const user of users) {
      await ctx.db.patch(user._id, {
        searchField: `${user.name} ${user.email}`,
      });
    }
  },
});
  1. Create a search index on this combined field:
export default defineSchema({
  users: defineTable({
    name: v.string(),
    email: v.string(),
    searchField: v.string(),
    // other fields...
  }).searchIndex("search_combined", {
    searchField: "searchField",
  }),
});
  1. Then search using this combined field:
const results = await ctx.db
  .query("users")
  .withSearchIndex("search_combined", (q) => q.search("searchField", searchTerm))
  .collect();

Remember that for small tables (less than a few thousand documents), using the filter approach is fine. For larger tables, consider using indexes or search indexes for better performance.

Stack Convex: Translate SQL into Convex Queries provides more examples of filtering with indexes.

Stack Convex: Searching for Sanity shows how to implement full-text search in Convex.

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

#

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

meager rapids
#

@sudden ember how about pagination combined with the search?

sudden emberBOT
#

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

#

I understand you're asking about how to combine pagination with search functionality in Convex. Let me explain the options available based on your previous questions about filtering across multiple fields.

Pagination with Filter-Based Search

When using the filter method with pagination, you can do:

export const list = query({
  args: { search: v.string(), paginationOpts: paginationOptsValidator },
  handler: async (ctx, args) => {
    return ctx.db
      .query("users")
      .filter((q) => 
        q.or(
          q.field("name").includes(args.search),
          q.field("email").includes(args.search)
        )
      )
      .paginate(args.paginationOpts);
  },
});

However, be aware that this approach might return small or empty pages initially because the filtering happens after pagination. As mentioned in your previous question, you might need to increase the initialNumItems value when using usePaginatedQuery.

Pagination with Full-Text Search

You can also paginate search results when using full-text search:

export const searchUsers = query({
  args: { search: v.string(), paginationOpts: paginationOptsValidator },
  handler: async (ctx, args) => {
    return ctx.db
      .query("users")
      .withSearchIndex("search_name", (q) => q.search("name", args.search))
      .paginate(args.paginationOpts);
  },
});

The Convex documentation confirms that search results can be paginated: "Additionally, search results can be paginated using .paginate(paginationOpts)" Search > Full Text Search > Running search queries.

Limitations with Multiple Search Indexes

#

When searching across multiple fields using search indexes, there's a limitation: you can't directly combine multiple withSearchIndex calls in a single query. As noted in a Discord support thread: "I would start with two withSearchIndex calls in the same convex query, and merge the results" Discord support thread: Full-text search across fields in different tables.

For this scenario, you might need to:

  1. Run separate paginated queries for each field
  2. Merge the results in your code
  3. Implement your own pagination logic on the merged results

Using a Calculated Field with Pagination

If you've implemented the calculated field approach (combining multiple fields into one searchable field), pagination works normally:

export const searchCombined = query({
  args: { search: v.string(), paginationOpts: paginationOptsValidator },
  handler: async (ctx, args) => {
    return ctx.db
      .query("users")
      .withSearchIndex("search_combined", (q) => q.search("searchField", args.search))
      .paginate(args.paginationOpts);
  },
});

Best Practices

For the best performance with pagination and search:

  1. Use indexes whenever possible for large tables Understanding > Best-practices > Other Recommendations
  2. Be aware that .collect() will throw an exception if it attempts to collect more than 1024 documents, so pagination is recommended for large result sets
  3. Consider using the Convex Aggregate component for more advanced pagination needs, including offset-based pagination Discord support thread: We need numbered pagination
#

Remember that the approach you choose should depend on your specific use case, data volume, and performance requirements.

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