Web Dev Cody
Web Dev Cody•3mo ago

Understanding usePaginatedQuery

I'm trying to wrap my head around how to use a page based usePaginatedQuery, so basically if a user hits /posts?page=1 or /posts?page=2, I want to fetch 5 items and change the offset.
the usePaginatedQuery seems to be more like a lazy scroll type of thing. Maybe I'm missing something?
11 Replies
Convex Bot
Convex Bot•3mo ago
Thanks for posting in <#1088161997662724167>. Reminder: If you have a Convex Pro account, use the Convex Dashboard to file support tickets. - Provide context: What are you trying to achieve, what is the end-user interaction, what are you seeing? (full error message, command output, etc.) - Use search.convex.dev to search Docs, Stack, and Discord all at once. - Additionally, you can post your questions in the Convex Community's <#1228095053885476985> channel to receive a response from AI. - Avoid tagging staff unless specifically instructed. Thank you!
lee
lee•3mo ago
yes usePaginatedQuery isn't really conducive to offset-based pagination. the problem is you have to know where in the table to start, and we can't just tell the database "skip over 500 documents, then give a page of size 50" if you want the pages to be defined by a field, like each page is a day's worth of documents, you can query and filter against the by_creation_time index if you actually want offset-based pagination, you can use the Aggregate component because one you have aggregates, it's feasilbe to say "skip over 500 documents"
Hmza
Hmza•3mo ago
i faced same thing a little while ago so i came up with this https://github.com/hamzasaleem2/convex-tanstack-table/blob/main/src/useSimplePaginatedQuery.ts its just a wrap over useQuery but did the job for me for offsets it gives you this hook to use:
const {
status,
currentResults,
loadNext,
loadPrev,
currentPageNum,
setPageSize,
pageSize
} = useSimplePaginatedQuery(myQuery, initialArgs, { initialNumItems: 10 });
const {
status,
currentResults,
loadNext,
loadPrev,
currentPageNum,
setPageSize,
pageSize
} = useSimplePaginatedQuery(myQuery, initialArgs, { initialNumItems: 10 });
tanstack table demo: https://convex-tanstack-table.vercel.app/ i didn't worked out alot of things in this which can be better. but you are welcome to try and happy to take a feedback on this too.
GitHub
convex-tanstack-table/src/useSimplePaginatedQuery.ts at main · hamz...
Contribute to hamzasaleem2/convex-tanstack-table development by creating an account on GitHub.
Web Dev Cody
Web Dev CodyOP•3mo ago
@Hmza would the query on the convex query just need to return a pagination object?
lee
lee•3mo ago
^ that helper looks great and is the pattern i would recommend if you're okay with calling loadNext until you get to the page you want
Web Dev Cody
Web Dev CodyOP•3mo ago
easiest approach I can think is just add an auto incrementing id to the records and then calculate the page / offset using that with a .take max page length?
lee
lee•3mo ago
if auto incrementing id is feasible (no deletes, ordering by creation time) that ^ is equivalent to and better than going all-in on aggregates
Web Dev Cody
Web Dev CodyOP•3mo ago
yeah I just want a list of blog posts by date order desc going to add this export const addAutoIncrementingIdToPosts = internalMutation({ args: {}, async handler(ctx) { const posts = await ctx.db.query("blogPosts").order("desc").collect(); await Promise.all( posts.map((post, index) => ctx.db.patch(post._id, { incrementingId: index + 1 }) ) ); }, }); run it, then just do a take(PAGE_SIZE)
lee
lee•3mo ago
how will you calculate the index for new blog posts? (i'm asking because this looks like it won't work, unless the set of blog posts is fixed or you recalculate the incrementingId field whenever a new one is added) i.e. looks like this will generate incrementingId: 1 for the most recent blog post, incrementingId: 2 for the next, etc. so if you want to add a new blog post and keep the ordering, it would need to have incrementingId: 0
Web Dev Cody
Web Dev CodyOP•3mo ago
sorry I've modified since posting
const PAGE_SIZE = 5;

export const getPosts = query({
args: {
page: v.number(),
},
handler: async (ctx, args) => {
const startId = (args.page - 1) * PAGE_SIZE + 1;

const page = await ctx.db
.query("blogPosts")
.withIndex("incrementingId_index", (q) =>
q.gte("incrementingId", startId)
)
.take(PAGE_SIZE);

return page;
},
});
const PAGE_SIZE = 5;

export const getPosts = query({
args: {
page: v.number(),
},
handler: async (ctx, args) => {
const startId = (args.page - 1) * PAGE_SIZE + 1;

const page = await ctx.db
.query("blogPosts")
.withIndex("incrementingId_index", (q) =>
q.gte("incrementingId", startId)
)
.take(PAGE_SIZE);

return page;
},
});
I could also probably just do order('desc') so that id 1 is the oldest I think or just don't use ids and instead use proper dates
lee
lee•3mo ago
you're right; looks like it does work. carry on 🙂

Did you find this page helpful?