T
TanStack10mo ago
xenial-black

Is there a way to reset mutations based on key?

My apologies - this is a duplicate of https://discord.com/channels/719702312431386674/1050424579920773180 but that didn't have an answer. I am pretty sure I have a legit use case for this: I have a hook that keeps track of pending & successful mutations:
export function useLeftConvos() {
const pending = useMutationState({
filters: {mutationKey: [RQKEY_ROOT], status: 'pending'},
select: mutation => mutation.options.mutationKey?.[1] as string | undefined,
})
const success = useMutationState({
filters: {mutationKey: [RQKEY_ROOT], status: 'success'},
select: mutation => mutation.options.mutationKey?.[1] as string | undefined,
})
return useMemo(
() => [...pending, ...success].filter(id => id !== undefined),
[pending, success],
)
}
export function useLeftConvos() {
const pending = useMutationState({
filters: {mutationKey: [RQKEY_ROOT], status: 'pending'},
select: mutation => mutation.options.mutationKey?.[1] as string | undefined,
})
const success = useMutationState({
filters: {mutationKey: [RQKEY_ROOT], status: 'success'},
select: mutation => mutation.options.mutationKey?.[1] as string | undefined,
})
return useMemo(
() => [...pending, ...success].filter(id => id !== undefined),
[pending, success],
)
}
I can then use this hook to filter out items from a list elsewhere. Sometimes, I want to remove an ID from this list. Is there a way to reset a mutation, like how you can do queryClient.resetQueries({ queryKey: your_key_here })? I can't use the reset function returned by the hook, because I don't know what the ID is in advance. If you're interested, the mutation is here - it's for leaving a DM conversation: https://github.com/bluesky-social/social-app/blob/main/src/state/queries/messages/leave-conversation.ts and I want to reset it when a new conversation begins, here: https://github.com/bluesky-social/social-app/blob/main/src/state/queries/messages/list-conversations.tsx#L105 Many thanks!
11 Replies
optimistic-gold
optimistic-gold10mo ago
So I think my understanding is that you want to remove the mutation because if a user leaves and re-joins the convo, the leave convo mutation is still filtering out the convo they just re-joined and it won't show in there DMs list (going off code here https://github.com/DogPawHat/social-app/blob/3409a725484a5560a14987c03db7720a02834e06/src/screens/Messages/ChatList.tsx#L103) I don't think there's any hook or anything to help you here. I think this might work, but I'd like to know what your test case is for this
// line 106, list-conversation.tsx
const mutationCache = queryClient.getMutationCache()
// `find` is undocumeneted but it's been there sense v4, same with remove
const mutationToReset = mutationCache.find({
mutationKey: LEAVE_CONVO_KEY(log.convoId),
})

if (mutationToReset) {
mutationCache.remove(mutationToReset)
}

debouncedRefetch()
// line 106, list-conversation.tsx
const mutationCache = queryClient.getMutationCache()
// `find` is undocumeneted but it's been there sense v4, same with remove
const mutationToReset = mutationCache.find({
mutationKey: LEAVE_CONVO_KEY(log.convoId),
})

if (mutationToReset) {
mutationCache.remove(mutationToReset)
}

debouncedRefetch()
Also, I assume this is an optimistic update pattern as described here https://tanstack.com/query/latest/docs/framework/react/guides/optimistic-updates But you seem to be doing UI optimistic updates in ChatList and cache optimistic updates with setQueryData in useLeaveConvo. I thought it was either/or? This is at the limit of my own knowledge and just taking 20 mins to look though both the query and bsky code, so you and anyone else has an open invitation to tell me if I'm horrifically wrong.
Optimistic Updates | TanStack Query React Docs
React Query provides two ways to optimistically update your UI before a mutation has completed. You can either use the onMutate option to update your cache directly, or leverage the returned variables...
GitHub
social-app/src/screens/Messages/ChatList.tsx at 3409a725484a5560a14...
The Bluesky Social application for Web, iOS, and Android - DogPawHat/social-app
optimistic-gold
optimistic-gold10mo ago
I ended up making a pr to remove useLeftConvos entirely as I'm 80% sure it's reduntant code becasue of the cache updating. https://github.com/bluesky-social/social-app/pull/7874
GitHub
Remove use left convos by DogPawHat · Pull Request #7874 · bluesky-...
Follow on from comments in the Tanstack discord made by @mozzius about useMutationState. I've attempted to give a more "straightforward" answer over there, but I'm...
optimistic-gold
optimistic-gold10mo ago
Only other way to fix it if you really need to keep useLeftConvos is to do some jank filtering:
// leave-conversations.ts
export function useLeftConvos() {
const pending = useMutationState({
filters: {mutationKey: [RQKEY_ROOT], status: 'pending'},
select: mutation => ({
submittedAt: mutation.state.submittedAt,
id: mutation.options.mutationKey?.[1] as string | undefined,
}),
})
const success = useMutationState({
filters: {mutationKey: [RQKEY_ROOT], status: 'success'},
select: mutation => ({
submittedAt: mutation.state.submittedAt,
id: mutation.options.mutationKey?.[1] as string | undefined,
}),
})

return [...pending, ...success].filter(mutation => mutation.id !== undefined)
}

// ChatList.tsx
const conversations = useMemo(() => {
if (data?.pages) {
return (
data.pages
.flatMap(page => page.convos)
// filter out convos that are actively being left
.filter(convo => {
const leftConvo = leftConvos.find(convo => convo.id === convo.id)
if (leftConvo) {
const leftAt = leftConvo.submittedAt

const lastMessage = convo.lastMessage
if (ChatBskyConvoDefs.isMessageView(lastMessage)) {
return new Date(lastMessage.sentAt).getTime() > leftAt
}

return false
}
return true
})
)
}
return []
}, [data, leftConvos])
// leave-conversations.ts
export function useLeftConvos() {
const pending = useMutationState({
filters: {mutationKey: [RQKEY_ROOT], status: 'pending'},
select: mutation => ({
submittedAt: mutation.state.submittedAt,
id: mutation.options.mutationKey?.[1] as string | undefined,
}),
})
const success = useMutationState({
filters: {mutationKey: [RQKEY_ROOT], status: 'success'},
select: mutation => ({
submittedAt: mutation.state.submittedAt,
id: mutation.options.mutationKey?.[1] as string | undefined,
}),
})

return [...pending, ...success].filter(mutation => mutation.id !== undefined)
}

// ChatList.tsx
const conversations = useMemo(() => {
if (data?.pages) {
return (
data.pages
.flatMap(page => page.convos)
// filter out convos that are actively being left
.filter(convo => {
const leftConvo = leftConvos.find(convo => convo.id === convo.id)
if (leftConvo) {
const leftAt = leftConvo.submittedAt

const lastMessage = convo.lastMessage
if (ChatBskyConvoDefs.isMessageView(lastMessage)) {
return new Date(lastMessage.sentAt).getTime() > leftAt
}

return false
}
return true
})
)
}
return []
}, [data, leftConvos])
...yeah. I almost don't want any part of this lol.
xenial-black
xenial-blackOP9mo ago
ya it’s horrific, I know. the reason I wanted useLeftConvos is that the API for leaving convos is a bit flaky and can take a really long time. so oftentimes the list will refresh while the request is still in flight, meaning it shows up again after the optimistic update thanks for the PR though I’ll take a closer look
optimistic-gold
optimistic-gold9mo ago
Oh yikes you mean it gets refetched between onMutate and onSuccess/onError/onSettled. yuck.
optimistic-gold
optimistic-gold9mo ago
(I also ended up finding another issue with your chat while I was testing this yay https://github.com/bluesky-social/social-app/issues/7882)
GitHub
Issues · bluesky-social/social-app
The Bluesky Social application for Web, iOS, and Android - Issues · bluesky-social/social-app
optimistic-gold
optimistic-gold9mo ago
Latest harebrained plan I have; call cancelQueries in the onMutate handle to cancel any queries, and disable the list query hook while leave is outbound:
const leaveConvoMutationStates = useMutationState({
filters: {
mutationKey: [LEAVE_CONVO_RQKEY_ROOT],
status: 'pending',
},
})

return useInfiniteQuery({
enabled: enabled && leaveConvoMutationStates.length === 0,
queryKey: RQKEY,
queryFn: async ({pageParam, signal}) => {
const {data} = await agent.api.chat.bsky.convo.listConvos(
{cursor: pageParam, limit: 20},
{headers: DM_SERVICE_HEADERS, signal},
)

return data
},
initialPageParam: undefined as RQPageParam,
getNextPageParam: lastPage => lastPage.cursor,
})
const leaveConvoMutationStates = useMutationState({
filters: {
mutationKey: [LEAVE_CONVO_RQKEY_ROOT],
status: 'pending',
},
})

return useInfiniteQuery({
enabled: enabled && leaveConvoMutationStates.length === 0,
queryKey: RQKEY,
queryFn: async ({pageParam, signal}) => {
const {data} = await agent.api.chat.bsky.convo.listConvos(
{cursor: pageParam, limit: 20},
{headers: DM_SERVICE_HEADERS, signal},
)

return data
},
initialPageParam: undefined as RQPageParam,
getNextPageParam: lastPage => lastPage.cursor,
})
xenial-black
xenial-blackOP9mo ago
lmao - that's actually not the worst idea tbh, I just need to nag the backend guys into fixing the flaky API
optimistic-gold
optimistic-gold9mo ago
I'll throw it in the pr and spam my friend with leave convos to see if it breaks anything. Will be up to you what to do. As for the backend folks, find user issues and spam them there. Or fix the go code yourself lol.
xenial-black
xenial-blackOP9mo ago
basically we know exactly what the issue with the API is, but to fix it we need to run a script to clean up a bunch of junk in the database. and we need to release an app version that stops adding junk to the database before we can do that I will merge your PR once that’s all done :) because it is the correct method
optimistic-gold
optimistic-gold9mo ago
Ah migration hell lol Pleased you find it useful. I'll make sure to play a bit with the app repo in the future.

Did you find this page helpful?