-
Is there a way to cancel/abort mutation requests, similar to how I can't seem to figure that out. |
Beta Was this translation helpful? Give feedback.
Replies: 21 comments 36 replies
-
I don't think there is. The thing is: But as soon as you've fired off a mutation, once the backend has received it and has potentially started to update a database entry - how would you cancel that from the frontend... 🤷 . Can you maybe elaborate on your use-case - what do you want to achieve? |
Beta Was this translation helpful? Give feedback.
-
I just came across a similar use-case to @Merott’s but also use different debouncer delays based on the initiator (400ms for pointer and 1000ms for keyboard events). So, each Promise used for the mutation is cancelable on its own, throwing a custom cancelation error when the Noticing that the retryer already has a What do you think about this proposal? (I didn’t want to open yet another thread about this, but would be excited to see a similar concept implemented into the project.) |
Beta Was this translation helpful? Give feedback.
-
There are a lot of cases for abortable mutations. For example, I'm sending files to the backend and my server can stop all the unnecessary work when request is aborted. I think if mutationFunction returns a promise with a |
Beta Was this translation helpful? Give feedback.
-
Yes, I think so. Here is how I think what would need to happen:
I think the react-query involvement is not the most complex thing in this chain. For example, what happens if the mutation is cancelled after the database transaction has already been committed, but before the network request has been completed? What happens if the mutation is just a "send an email somewhere", how would you cancel that? And so on... So yeah, I would actually accept a PR that adds mutation cancellation via the |
Beta Was this translation helpful? Give feedback.
-
Hello I am interested what's happening with this mutation cancelling ? I can see that in useQueryClient there is a function for canceling mutation (cancelMutation()), I tried to use on a similar way as the cancelQuery(), the request is still working, but there is an error u console that informs me that the request has been canceled. Is it the expected behavior? |
Beta Was this translation helpful? Give feedback.
-
It is trivial to write a wrapper to cancel mutations but it would be ideal if it was in built. interface IFileUploadParams {
presignedUploadUrl: string
file: Blob
}
export const useFileUploadMutation = (
options?: Omit<
UseMutationOptions<void, AxiosError, IFileUploadParams>,
'mutationFn'
>
) => {
const [progress, setProgress] = useState(0)
const abortControllerRef = useRef<AbortController | null>(null)
const mutation = useMutation<void, AxiosError, IFileUploadParams>((args) => {
abortControllerRef.current = new AbortController()
return axios.put(args.presignedUploadUrl, args.file, {
onUploadProgress: (ev) => {
setProgress(Math.round((ev.loaded * 100) / ev.total))
},
signal: abortControllerRef.current.signal,
})
}, options)
const reset = useCallback(() => {
abortControllerRef.current?.abort()
mutation.reset()
setProgress(0)
}, [mutation.reset, setProgress])
return { ...mutation, progress, abortControllerRef, reset }
} |
Beta Was this translation helpful? Give feedback.
-
@nathanhannig if you use the callbacks on .mutate for component specific side effects, they won't execute when the component has already unmounted. This is documented here and I've also mentioned it here: Further, the specifically mentioned warning has been removed in react 18 because it was a false positive most of the time, such as in the example that you describe. see: reactwg/react-18#82 |
Beta Was this translation helpful? Give feedback.
-
cough
|
Beta Was this translation helpful? Give feedback.
-
@TkDodo In my case, I have this use case. I want to abort / remove the mutation when I'm offline. https://stackoverflow.com/questions/73444102/react-query-cancel-abort-mutation-if-paused-offline |
Beta Was this translation helpful? Give feedback.
-
I have a tool for swagger auto generator https://github.com/hosseinmd/swagger-typescript by this tool we could generate react hook from swagger which react-query is under the hood. So for canceling APIs, I was added a cancel function to all APIs promises. https://github.com/hosseinmd/swagger-typescript/blob/c4b74a56c847e4ae34d5d1b9e9527cc80024d5c2/src/javascript/files/httpRequest.ts#L26 |
Beta Was this translation helpful? Give feedback.
-
I'm not sure if this is what the original poster wanted but I can imagine cancelling a use case for cancelling a query from // trpc.delete is a trpc mutation
// confirmPrompt is a promise that returns the boolean selection from a user prompt.
const {mutateAsync} = trpc.delete.useMutation({
onMutate: (data, variables, error, ctx) {
if (!confirmPrompt('Do you want to delete the object?')) {
ctx.cancelMutation()
}
}
})
return <button onClick={mustateAsync}>Delete Object</button> This this something you would consider supporting @TkDodo ? |
Beta Was this translation helpful? Give feedback.
-
@TkDodo You're being very helpful regarding working around the issue, but seem uninterested in actually solving it.
My mutations are idempotent, so there's no problem here. Like some others here, I was just hoping for an easy way to debounce my requests while still being able to use the cache for optimistic updates using the very pattern from the Optimistic Updates documentation page:
My life would be very easy if there an That said: Thanks for all the great work. Tanstack Query is great and so is your guide! I just think it could make our lives yet a bit better on this front. |
Beta Was this translation helpful? Give feedback.
-
Very late to the party, I know. My suggestion would be to send a UNIX timestamp with the updated value, and use that to avoid race conditions on the backend. Canceling the request doesn't help the backend much, because it's a mutation. All it does is tell the front-end not to care about the response. An even fancier approach might involve you caching the patch data on the server to avoid redundant database calls within a short space of time, then invalidating the server cache. One tech to achieve this is redis. Anyhoo, I was thinking about this race-condition mutation stuff earlier today, and this is how I'll be doing it. I appreciate the conversation here. |
Beta Was this translation helpful? Give feedback.
-
Here is a case when we show each time the result of the mutation + debounce. https://evilmartians.com/chronicles/aborting-queries-and-mutations-in-react-apollo @TkDodo How would you handle this case with React-Query (only show the response of the latest mutation)? |
Beta Was this translation helpful? Give feedback.
-
My use is checking whether a user is logged-in in
I know that I could check the user session in the components, but I'm developing an internal app so I wanted to handle this use case as globally as possible. |
Beta Was this translation helpful? Give feedback.
-
I'd like to share my potential use case for abort signals in combination with mutations. I've developed a file upload widget that uploads files in 100 MB chunks to my backend server. Each chunk is sent sequentially to the backend using Axios, wrapped around a useMutation function. Sometimes, users may want to pause or even cancel the upload process. The widget provides the option to resume uploading files from the last successfully uploaded chunk. To achieve this, I've added my custom AbortSignal as an input to the mutation function, since useMutation doesn't natively support this feature. However, there's a challenge with my current approach. The issue is that TanStack Query doesn't have any awareness of my AbortSignal. When I trigger the AbortSignal, Axios effectively cancels the request. Nevertheless, I have configured retries in case of network connectivity issues, which means that all retry attempts need to be exhausted before the mutation completes, and I can implement my error-handling logic. Ideally, I want a way to cancel my mutation and also notify it that it should cease retrying when manually canceled. |
Beta Was this translation helpful? Give feedback.
-
Subscribed 👀 We also have some use cases where mutations need to be cancelled. Btw updates on the db could be easily rolled back after cancelation. |
Beta Was this translation helpful? Give feedback.
-
I think the biggest use-case for cancellable mutation has been mentioned above (and answered, I know… but more in a "can't do it for now, I'm afraid" kind of way): what if mutations pile up when offline, and we want to cancel overlapping or colliding mutations (to avoid race conditions when the app goes back online). 2 typical cases:
|
Beta Was this translation helpful? Give feedback.
-
I also have an offline first app. An official pattern to do something like the following would be very handy: If a post is deleted, remove all previous mutations using a filter. It doesn't matter if one of the previous mutations reached the server as the latest mutation will be removing the post altogether. That is, if it even existed in the database to begin with. Not removing the previous mutations will cause lots of redundant requests, and in my case temporarily shows deleted posts and then removes them again as the user goes back online and mutations are run in order. For now, I've implemented something similar to @James-Whitfield's solution in the delete onMutate callback, which appears to be working as hoped: ...
onMutate: (deleteArray) => {
queryClient.cancelQueries({ queryKey: [journalEntriesKey] })
.then(() => JournalEntriesInfiniteQueryCache.deleteMultiple(deleteArray));
JournalEntryQueryCache.deleteMultiple(deleteArray);
const mutationCache = queryClient.getMutationCache();
mutationCache.getAll()
.filter((a: any) => {
let uuid = a.state?.variables?.uuid;
return uuid && deleteArray.includes(uuid) && a.state.isPaused;
})
.forEach(mutation => mutationCache.remove(mutation));
},
... The docs state that |
Beta Was this translation helpful? Give feedback.
-
My initial approach to prevent duplicate requests in React StrictMode and cancel requests if the component gets unmounted function MyCp () {
const abortCtrlRef = useRef<AbortController[]>([]);
const mutation = useMutation({
mutationKey: ['myMutationKey'],
mutationFn: () => {
abortCtrlRef.current.forEach(ctrl => ctrl.abort());
abortCtrlRef.current = [];
abortCtrlRef.current.push(new AbortController());
return axios.post('/myApi', payload, { signal: abortCtrlRef.current[0].signal })
},
});
useEffect(
() => () => {
abortCtrlRef.current.forEach(c => c.abort());
},
[],
);
} |
Beta Was this translation helpful? Give feedback.
-
If you want to use another library: import { E_CANCELED, Mutex } from 'async-mutex'
const mutex = new Mutex()
const { mutateAsync, isPending } = useMutation({
mutationKey: ['/exclusive'],
mutationFn: async () => {
try {
mutex.cancel()
await mutex.waitForUnlock()
await mutex.runExclusive(async () => {
// This will only run one at a time and will cancel all previous calls by throwing an error
})
} catch (error: unknown) {
if (error === E_CANCELED) {
return
}
throw error
}
},
}) |
Beta Was this translation helpful? Give feedback.
I don't think there is. The thing is:
queries
have no side-effect on the server, so you can run them as often as you want and also cancel them if you are no longer interested in the result. Per default, react-query will only ignore the result, but you can also attach acancel
method to the Promise to really abort the network request.But as soon as you've fired off a mutation, once the backend has received it and has potentially started to update a database entry - how would you cancel that from the frontend... 🤷 .
Can you maybe elaborate on your use-case - what do you want to achieve?