Guides
Query cancellation
Pass the queryFn's AbortSignal to fetch so requests nobody needs any more are aborted, and cancel queries yourself.
Every queryFn receives an AbortSignal. Pass it to fetch (or any API that takes a signal), and raf-query can abort the request when its result is no longer needed:
export function function searchQuery(input: {
q: string;
}): QueryOptions<Flat<{
data: any;
key: readonly ["search", string];
}>>
searchQuery(input: {
q: string;
}
input: { q: stringq: string }) {
return queryOptions<any, unknown, never, readonly ["search", string], undefined>(options: Omit<QueryOptions<{
data: any;
error: unknown;
key: readonly ["search", string];
services: never;
}>, "kind" | "throwOnError"> & ThrowOnErrorField<undefined>): QueryOptions<Flat<{
data: any;
key: readonly ["search", string];
}>>
queryOptions({
queryKey: readonly ["search", string]queryKey: ["search", input: {
q: string;
}
input.q: stringq],
function queryFn(context: QueryFunctionContext): anyqueryFn: async (context: QueryFunctionContextcontext) => {
const const response: Responseresponse = await function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>fetch(
`/api/search?q=${function encodeURIComponent(uriComponent: string | number | boolean): stringencodeURIComponent(input: {
q: string;
}
input.q: stringq)}`,
{
RequestInit.signal?: AbortSignal | null | undefinedsignal: context: QueryFunctionContextcontext.QueryFunctionContext.signal: AbortSignalsignal,
},
);
return const response: Responseresponse.Body.json(): Promise<any>json();
},
});
}A queryFn that ignores the signal still works. Its request just runs to the end, and the result is cached.
When requests are aborted
raf-query is conservative about aborting, because a result that arrives is usually still worth caching:
- The last reader leaves during a background refetch. If the
queryFnread itssignal, the request is aborted, and the entry goes back to how it was before it: same data, not fetching.fetchcallers waiting on it get the cached data. - The entry is removed or cleared (
remove,clear,reset). Its request is aborted, andfetch/ensurecallers reject with anAbortError, as an abortedfetchwould.
Some requests are never aborted for losing their readers:
- First loads. A render may be suspended on the result, and it will be needed as soon as anything reads the query again.
- Requests nothing has read yet, like a loader's
prefetch. The component that will read it may not have mounted yet.
These finish so their result is cached, but don't retry on failure unless a reader comes back.
Try it below. The inbox's queryFn passes its signal to the server, so aborted requests show up in the Queries panel:
Cancelling yourself
To stop a request yourself, remove the entry: the request is aborted and the data dropped. To keep the data and only abort, there's no direct call; in practice, letting a request finish and caching its result is almost always what you want.
Effect
An Effect queryFn doesn't need the signal: aborting the request interrupts the fiber, which runs its finalizers and interrupts any child work. The signal is still passed, for APIs in the Effect that take one. See Effect.
Using axios or other clients
Clients that accept a signal work the same way:
queryOptions<unknown, unknown, never, readonly ["todos", number], undefined>(options: Omit<QueryOptions<{
data: unknown;
error: unknown;
key: readonly ["todos", number];
services: never;
}>, "kind" | "throwOnError"> & ThrowOnErrorField<undefined>): QueryOptions<Flat<{
data: unknown;
key: readonly ["todos", number];
}>>
queryOptions({
queryKey: readonly ["todos", number]queryKey: ["todos", const input: {
id: number;
}
input.id: numberid],
function queryFn(context: QueryFunctionContext): unknownqueryFn: (context: QueryFunctionContextcontext) =>
const axios: {
get(url: string, options: {
signal: AbortSignal;
}): Promise<unknown>;
}
axios.function get(url: string, options: {
signal: AbortSignal;
}): Promise<unknown>
get(`/api/todos/${const input: {
id: number;
}
input.id: numberid}`, { signal: AbortSignalsignal: context: QueryFunctionContextcontext.QueryFunctionContext.signal: AbortSignalsignal }),
});
For clients that take a cancel function instead, call it from an abort listener:
queryOptions<unknown, unknown, never, readonly ["todos", number], undefined>(options: Omit<QueryOptions<{
data: unknown;
error: unknown;
key: readonly ["todos", number];
services: never;
}>, "kind" | "throwOnError"> & ThrowOnErrorField<undefined>): QueryOptions<Flat<{
data: unknown;
key: readonly ["todos", number];
}>>
queryOptions({
queryKey: readonly ["todos", number]queryKey: ["todos", const input: {
id: number;
}
input.id: numberid],
function queryFn(context: QueryFunctionContext): unknownqueryFn: (context: QueryFunctionContextcontext) => {
const const request: {
cancel(): void;
promise: Promise<unknown>;
}
request = const client: {
get(url: string): {
cancel(): void;
promise: Promise<unknown>;
};
}
client.function get(url: string): {
cancel(): void;
promise: Promise<unknown>;
}
get(`/todos/${const input: {
id: number;
}
input.id: numberid}`);
context: QueryFunctionContextcontext.QueryFunctionContext.signal: AbortSignalsignal.AbortSignal.addEventListener<"abort">(type: "abort", listener: (this: AbortSignal, ev: Event) => any, options?: boolean | AddEventListenerOptions): void (+1 overload)addEventListener("abort", () => const request: {
cancel(): void;
promise: Promise<unknown>;
}
request.function cancel(): voidcancel());
return const request: {
cancel(): void;
promise: Promise<unknown>;
}
request.promise: Promise<unknown>promise;
},
});