Pagination + Optimistic updates
I have two questions, one exclusively related to pagination and another with combines pagination with optimistic updates.
Basically I am building a chat in my applicatin so team members can talk to each other.
1) In the code below you can see my convex function to return members form a project which should go to the chat members list. But I have to populate the user data field so thats why I have the promise all, when I try to implement it with pagination using the documentation in the convex web I get an error as I can't iterate a PaginatedREsult (I belive its call like that), and if I access the data then the paginated query throws an error:
export const getMembers = query({
args: {
projectId: v.id('projects'),
},
handler: async (ctx, args) => {
const access = await accessToProject(
ctx,
args.projectId,
'juanillaberia2002@gmail.com'
);
if (!access) return [];
const projectMembers = await ctx.db
.query('project_members')
.withIndex('by_projectId', q => q.eq('projectId', args.projectId))
.collect();
// const projectMembers = await ctx.db
// .query('project_members')
// .withIndex('by_projectId', q => q.eq('projectId', args.projectId))
// .paginate(args.paginationOpts);
return await Promise.all(
projectMembers.map(async member => {
const userData = await ctx.db.get(member.userId);
return {
...userData,
role: member.role,
};
})
);
},
});
2) And second, is it possible to implement optimistic updates and also paginate messages? I successfully implemented the optimistic updates, but I am not quite sure how to add the pagination (because of the question 1 and also if they go well together or their are meant to be separated features) as messages contain the userId who sent it and I need to query for the image and the name.
Thank you so much for your time! 🙂
9 Replies
for 2) there's this https://docs.convex.dev/api/modules/react#optimisticallyupdatevalueinpaginatedquery
for (1) i think @Michal Srb can help 🙂
Module: react | Convex Developer Hub
Tools to integrate Convex into React applications.
1.
I'll make sure we add this to our docs!
2. To add to @lee :
Thanks you @lee and @Michal Srb . I'll try to implement it now
I was able to implement the pagination. But the optimistic updates in combination with the pagination is not working for me. Its now throwing any error but the mmessages are not being added with the optimistic. My implementation:
//get messages function
export const getMessages = query({
args: {
projectId: v.id('projects'),
paginationOpts: paginationOptsValidator,
},
handler: async (ctx, args) => {
const messages = await ctx.db
.query('messages')
.withIndex('by_projectId_time', q => q.eq('projectId', args.projectId))
.order('desc')
.paginate(args.paginationOpts);
return {
...messages,
page: await Promise.all(
messages.page.map(async msg => {
const userData = await ctx.db.get(msg.sendBy);
return {
...msg,
sendBy: {
...userData,
},
};
})
),
};
},
});
//This is the conversation page:
const Conversation = ({ projectId }: { projectId: Id<'projects'> }) => {
const { results, loadMore, status } = usePaginatedQuery(
api.messages.getMessages,
{ projectId },
{
initialNumItems: 1,
}
);
if (status === 'LoadingFirstPage')
return (
<ul className='flex flex-col flex-1 gap-6 w-full'>
<MessageLoader />
<MessageLoader />
<MessageLoader />
<MessageLoader />
<MessageLoader />
</ul>
);
console.log(results);
return (
<ul className='flex-1 w-full overflow-y-auto'>
<button onClick={() => loadMore(1)}>MORE</button>
{results.length > 0 ? (
results
.sort((a, b) => a._creationTime - b._creationTime)
.map(msg => (
<Message
messageData={msg}
senderData={msg.sendBy}
isFirstInGroup={true}
/>
))
) : (
<p className='flex justify-center items-center h-full text-sm text-center text-text-2'>
No messages yet
</p>
)}
</ul>
);
};
export default Conversation;
//This is where I send the messages
const ChatInput = ({
projectId,
userEmail,
}: {
projectId: Id<'projects'>;
userEmail: string;
}) => {
//Message value
const [message, setMessage] = useState('');
const sendMessage = useMutation(
api.messages.sendMessage
).withOptimisticUpdate((localStore, mutationArg) => {
optimisticallyUpdateValueInPaginatedQuery(
localStore,
api.messages.getMessages,
{ projectId },
crrVal => {
const crrUser = localStore.getQuery(api.users.getUserByEmail, {
email: userEmail,
});
const newMessage = {
_id: crypto.randomUUID() as Id<'messages'>,
_creationTime: Date.now(),
projectId,
data: mutationArg.data,
sendBy: crrUser,
};
return {
...crrVal,
newMessage,
};
}
);
});
const handleSendMessage = async (
projectId: Id<'projects'>,
newMessageText: string
) => {
await sendMessage({ projectId: projectId, data: newMessageText });
};
return (
...
);
};
export default ChatInput;
Sorry for all the messages. Basiccaly I am able to paginate when I click the btn I get more messages all good, but the optimistic is taking the 'regular time', I am probably doing something wrongOh, rereading the doc for
optimisticallyUpdateValueInPaginatedQuery
, it's only useful for updating the existing pages, whereas you want to add a new item into the query.
I'll try to see if normal withOptimisticUpdate
will work
This is a bit more involved, I'll file this as a feature request. For now I'd stick to using React state and showing the "in-flight" message outside the paginated query results. When the paginated query results change you can reset the React state (within the render pass, so that the "in-flight" message and the message from the server aren't both rendered at the same time).Okey perfect, no worries. I'll try to make it work. Thanks!
@Michal Srb In your opinion, if I can only choose one of the convex feature, should it be the pagination or the optimistic updates?
It really depends on your app. Probably pagination, since that usually becomes a requirement once your data grows.
I was thinking the same. The optimistic, I may be able to implemented in the front end.
hey, seems its not possible also to delete form paginated query with optimistic update
@Starlord not possible as it
https://github.com/get-convex/convex-js/blob/main/src/react/use_paginated_query.ts#L533
has a bug? What do you expect, and what is happening?