Cancel requests #2330
Replies: 20 comments 2 replies
-
The hardest part about this is that every library has its own API. You can pass a controller to the fetch function but there’s no general way to do it inside SWR. And another question is when to cancel it (when unmounting a component?). |
Beta Was this translation helpful? Give feedback.
-
I think it’s way easier to ignore the result than cancel it with the current state of fetching libraries |
Beta Was this translation helpful? Give feedback.
-
For now a custom hook can (partially) solve it: function useCancelableSWR (key, opts) {
const controller = new AbortController()
return [useSWR(key, url => fetch(url, { signal: controller.signal }), opts), controller]
}
// to use it:
const [{ data }, controller] = useCancelableSWR('/api')
// ...
controller.abort() (also mentioned in #159) |
Beta Was this translation helpful? Give feedback.
-
You probably don't want to memoize the AbortController. You can only abort a controller instance once, so a new request requires a new controller. |
Beta Was this translation helpful? Give feedback.
-
Another thing to note is that |
Beta Was this translation helpful? Give feedback.
-
@strothj The issue is not whether the AbortController instance is properly cached or not. The abort controller should not be cached at all. |
Beta Was this translation helpful? Give feedback.
-
I did this with axios function useCancellableSWR(key, swrOptions) {
const source = axios.CancelToken.source();
return [
useSWR(key, (url) => axios.get(url, { cancelToken: source.token }).then(res => res.data), {
...swrOptions,
}),
source,
];
}
// usage:
const [{ data }, cancelFn] = useCancellableSWR('/endpoint');
cancelFn.cancel() |
Beta Was this translation helpful? Give feedback.
-
Production-ready solutionNote: An AbortController is created with each request export function useSWRAbort<Data = any, Error = any>(
key: keyInterface,
fn?: fetcherFn<Data>,
options?: ConfigInterface<Data, Error>
): responseInterface<Data, Error> & {
abort: () => void
} {
const aborter = useRef<AbortController>()
const abort = () => aborter.current?.abort()
const res = useSWR<Data, Error>(key, (...args) => {
aborter.current = new AbortController()
return fn?.(aborter.current.signal, ...args)
}, options)
return { ...res, abort }
} ExampleYour fetcher gets an extra param You can then pass it to your actual fetcher, for example const { data, error, abort } = useSWRAbort<T>(url, (signal, url) => {
return fetch(url, { signal }).then(res => res.json())
})
return <button onClick={abort}>
...
</button> Or use it to invalidate data afterwards const { data, error, abort } = useSWRAbort<T>(url, async (signal, url) => {
const res = await fetch(url)
if (signal.aborted) throw new Error("Aborted")
return await res.json()
}) Test with a timeoutconst { data, error, abort } = useSWRAbort<T>(url, async (signal, url) => {
await new Promise(ok => setTimeout(ok, 5000))
console.log("aborted?", signal.aborted)
const res = await fetch(url, { signal })
return await res.json()
}) |
Beta Was this translation helpful? Give feedback.
-
Does config.isPaused() work for your cases? useSWR(key, fetcher, {
isPaused() {
return /* condition for dropped requests */
}
}) |
Beta Was this translation helpful? Give feedback.
-
could this also be solved by a middleware @shuding? |
Beta Was this translation helpful? Give feedback.
-
I was able to solve this when using Axios and the middleware like @pke suggests: https://swr.vercel.app/docs/middleware
|
Beta Was this translation helpful? Give feedback.
-
Not sure I understand how this works 😉 edit: Ok I get it now. However, shouldn't the cancelToken be defined inside the middleware? Otherwise you can always only have one request going and all others will be cancelled? |
Beta Was this translation helpful? Give feedback.
-
One more solution as a middleware that automatically aborts the request as soon as the key is changed or component is unmounted. const abortableMiddleware: Middleware = (useSWRNext) => {
return (key, fetcher, config) => {
// for each key generate new AbortController
// additionally the key might be serialised using unstable_serialize, depending on your usecases
const abortController = useMemo(() => new AbortController(), [key]);
// as soon as abortController is changed or component is unmounted, call abort
useEffect(() => () => abortController.abort(), [abortController]);
// pass signal to your fetcher in way you prefer
const fetcherExtended: typeof fetcher = (url, params) =>
fetcher(url, { ...params, signal: abortController.signal });
return useSWRNext(key, fetcherExtended, config);
};
}; It also possible to play with this in my sandbox. |
Beta Was this translation helpful? Give feedback.
-
I have a new proposal here: #1933, feedback welcome! |
Beta Was this translation helpful? Give feedback.
-
import { useEffect, useRef } from 'react';
import { Middleware, SWRHook, unstable_serialize } from 'swr';
export type CancellablePromise<T> = Promise<T> & {
cancel: (str?: string) => void;
};
export const cancelMiddleware: Middleware =
(useSWRNext: SWRHook) => (key, fetcher, config) => {
const cancelRef = useRef<() => void>();
const keyString = unstable_serialize(key);
const extendsFetcher = fetcher
? (...rest: any) => {
const request = fetcher(...rest) as CancellablePromise<any>;
cancelRef.current = request.cancel;
return request;
}
: fetcher;
const swr = useSWRNext(key, extendsFetcher, config);
useEffect(() => {
return () => {
cancelRef.current?.();
};
}, [keyString]);
return swr;
}; The key needs to be serialized |
Beta Was this translation helpful? Give feedback.
-
Let's discuss this on #2312 |
Beta Was this translation helpful? Give feedback.
-
I want to thank everyone for your suggestions. Here are my two cents:
|
Beta Was this translation helpful? Give feedback.
-
I wanted to suggest the |
Beta Was this translation helpful? Give feedback.
-
Using |
Beta Was this translation helpful? Give feedback.
-
If I understand correctly, the implementations here will interact poorly with request deduplication. For example:
See #2275 for an implementation that uses reference counting to work even if requests are deduplicated. |
Beta Was this translation helpful? Give feedback.
-
There are often times in a web application where you need to send a request for the latest user interaction. We need a way to prevent subsequent (async) logic from running for all but the most recent request – something like Axios' cancel token or fetch()'s AbortController.
Beta Was this translation helpful? Give feedback.
All reactions