Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/good-buttons-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"openapi-react-query": patch
---

Fixed query key collision between different openapi-fetch clients.
38 changes: 21 additions & 17 deletions packages/openapi-react-query/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import {
type UseMutationOptions,
type UseMutationResult,
type UseQueryOptions,
type UseQueryResult,
type UseSuspenseQueryOptions,
type UseSuspenseQueryResult,
type QueryClient,
type QueryFunctionContext,
type SkipToken,
useMutation,
useQuery,
useSuspenseQuery,
import type {
QueryClient,
QueryFunctionContext,
SkipToken,
UseMutationOptions,
UseMutationResult,
UseQueryOptions,
UseQueryResult,
UseSuspenseQueryOptions,
UseSuspenseQueryResult,
} from "@tanstack/react-query";
import type { ClientMethod, FetchResponse, MaybeOptionalInit, Client as FetchClient } from "openapi-fetch";
import { useMutation, useQuery, useSuspenseQuery } from "@tanstack/react-query";
import type { ClientMethod, Client as FetchClient, FetchResponse, MaybeOptionalInit } from "openapi-fetch";
import type { HttpMethod, MediaType, PathsWithMethod, RequiredKeysOf } from "openapi-typescript-helpers";

type InitWithUnknowns<Init> = Init & { [key: string]: unknown };
Expand All @@ -21,7 +19,7 @@ export type QueryKey<
Paths extends Record<string, Record<HttpMethod, {}>>,
Method extends HttpMethod,
Path extends PathsWithMethod<Paths, Method>,
> = readonly [Method, Path, MaybeOptionalInit<Paths[Path], Method>];
> = readonly [number, Method, Path, MaybeOptionalInit<Paths[Path], Method>];

export type QueryOptionsFunction<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
Method extends HttpMethod,
Expand Down Expand Up @@ -104,12 +102,18 @@ export interface OpenapiQueryClient<Paths extends {}, Media extends MediaType =
useMutation: UseMutationMethod<Paths, Media>;
}

const clientIds = new WeakMap<FetchClient<object, MediaType>, number>();
let lastId = 0;

// TODO: Add the ability to bring queryClient as argument
export default function createClient<Paths extends {}, Media extends MediaType = MediaType>(
client: FetchClient<Paths, Media>,
): OpenapiQueryClient<Paths, Media> {
const clientId = clientIds.get(client) ?? lastId++;
clientIds.set(client, clientId);

const queryFn = async <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>>({
queryKey: [method, path, init],
queryKey: [_clientId, method, path, init],
signal,
}: QueryFunctionContext<QueryKey<Paths, Method, Path>>) => {
const mth = method.toUpperCase() as Uppercase<typeof method>;
Expand All @@ -122,7 +126,7 @@ export default function createClient<Paths extends {}, Media extends MediaType =
};

const queryOptions: QueryOptionsFunction<Paths, Media> = (method, path, ...[init, options]) => ({
queryKey: [method, path, init as InitWithUnknowns<typeof init>] as const,
queryKey: [clientId, method, path, init as InitWithUnknowns<typeof init>] as const,
queryFn,
...options,
});
Expand Down
24 changes: 24 additions & 0 deletions packages/openapi-react-query/test/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,30 @@ describe("client", () => {
expectTypeOf(result.current.data).toEqualTypeOf<"select(true)">();
expectTypeOf(result.current.error).toEqualTypeOf<false | null>();
});

it("should treat different fetch clients as separate instances", async () => {
const fetchClient1 = createFetchClient<minimalGetPaths>({ baseUrl, fetch: fetchInfinite });
const fetchClient2 = createFetchClient<minimalGetPaths>({ baseUrl, fetch: fetchInfinite });
const client1 = createClient(fetchClient1);
const client11 = createClient(fetchClient1);
const client2 = createClient(fetchClient2);

renderHook(
() => {
useQueries({
queries: [
client1.queryOptions("get", "/foo"),
client11.queryOptions("get", "/foo"),
client2.queryOptions("get", "/foo"),
],
});
},
{ wrapper },
);

// client1 and client11 have the same fetch client, so they share the same query key
expect(queryClient.isFetching()).toBe(2);
});
});

describe("useQuery", () => {
Expand Down