From 373427ba6b63538495d5fc189bab9c4d5d20f48c Mon Sep 17 00:00:00 2001 From: Hagen Morano Date: Mon, 20 Jan 2025 11:51:32 +0100 Subject: [PATCH] fix(openapi-react-query): correctly infer select return value --- .changeset/chilly-meals-act.md | 5 +++ packages/openapi-react-query/src/index.ts | 42 +++++++++++++++---- .../openapi-react-query/test/index.test.tsx | 35 +++++++++++++++- 3 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 .changeset/chilly-meals-act.md diff --git a/.changeset/chilly-meals-act.md b/.changeset/chilly-meals-act.md new file mode 100644 index 000000000..6c60b5419 --- /dev/null +++ b/.changeset/chilly-meals-act.md @@ -0,0 +1,5 @@ +--- +"openapi-react-query": patch +--- + +[#1845](https://github.com/openapi-ts/openapi-typescript/pull/2105): The return value of the `select` property is now considered when inferring the `data` type. diff --git a/packages/openapi-react-query/src/index.ts b/packages/openapi-react-query/src/index.ts index cbee066c0..efd067043 100644 --- a/packages/openapi-react-query/src/index.ts +++ b/packages/openapi-react-query/src/index.ts @@ -15,6 +15,9 @@ import { import type { ClientMethod, FetchResponse, MaybeOptionalInit, Client as FetchClient } from "openapi-fetch"; import type { HttpMethod, MediaType, PathsWithMethod, RequiredKeysOf } from "openapi-typescript-helpers"; +// Helper type to dynamically infer the type from the `select` property +type InferSelectReturnType = TSelect extends (data: TData) => infer R ? R : TData; + type InitWithUnknowns = Init & { [key: string]: unknown }; export type QueryKey< @@ -29,7 +32,12 @@ export type QueryOptionsFunction, Response extends Required>, // note: Required is used to avoid repeating NonNullable in UseQuery types Options extends Omit< - UseQueryOptions>, + UseQueryOptions< + Response["data"], + Response["error"], + InferSelectReturnType, + QueryKey + >, "queryKey" | "queryFn" >, >( @@ -40,11 +48,21 @@ export type QueryOptionsFunction, Options?] ) => NoInfer< Omit< - UseQueryOptions>, + UseQueryOptions< + Response["data"], + Response["error"], + InferSelectReturnType, + QueryKey + >, "queryFn" > & { queryFn: Exclude< - UseQueryOptions>["queryFn"], + UseQueryOptions< + Response["data"], + Response["error"], + InferSelectReturnType, + QueryKey + >["queryFn"], SkipToken | undefined >; } @@ -56,7 +74,12 @@ export type UseQueryMethod>, Init extends MaybeOptionalInit, Response extends Required>, // note: Required is used to avoid repeating NonNullable in UseQuery types Options extends Omit< - UseQueryOptions>, + UseQueryOptions< + Response["data"], + Response["error"], + InferSelectReturnType, + QueryKey + >, "queryKey" | "queryFn" >, >( @@ -65,7 +88,7 @@ export type UseQueryMethod>, ...[init, options, queryClient]: RequiredKeysOf extends never ? [InitWithUnknowns?, Options?, QueryClient?] : [InitWithUnknowns, Options?, QueryClient?] -) => UseQueryResult; +) => UseQueryResult, Response["error"]>; export type UseSuspenseQueryMethod>, Media extends MediaType> = < Method extends HttpMethod, @@ -73,7 +96,12 @@ export type UseSuspenseQueryMethod, Response extends Required>, // note: Required is used to avoid repeating NonNullable in UseQuery types Options extends Omit< - UseSuspenseQueryOptions>, + UseSuspenseQueryOptions< + Response["data"], + Response["error"], + InferSelectReturnType, + QueryKey + >, "queryKey" | "queryFn" >, >( @@ -82,7 +110,7 @@ export type UseSuspenseQueryMethod extends never ? [InitWithUnknowns?, Options?, QueryClient?] : [InitWithUnknowns, Options?, QueryClient?] -) => UseSuspenseQueryResult; +) => UseSuspenseQueryResult, Response["error"]>; export type UseMutationMethod>, Media extends MediaType> = < Method extends HttpMethod, diff --git a/packages/openapi-react-query/test/index.test.tsx b/packages/openapi-react-query/test/index.test.tsx index 294175df9..d427372da 100644 --- a/packages/openapi-react-query/test/index.test.tsx +++ b/packages/openapi-react-query/test/index.test.tsx @@ -234,7 +234,7 @@ describe("client", () => { }); describe("useQuery", () => { - it("should resolve data properly and have error as null when successfull request", async () => { + it("should resolve data properly and have error as null when successful request", async () => { const response = ["one", "two", "three"]; const fetchClient = createFetchClient({ baseUrl }); const client = createClient(fetchClient); @@ -341,6 +341,39 @@ describe("client", () => { expectTypeOf(error).toEqualTypeOf<{ code: number; message: string } | null>(); }); + it("should infer correct data when used with select property", async () => { + const fetchClient = createFetchClient({ baseUrl, fetch: fetchInfinite }); + const client = createClient(fetchClient); + + const { result } = renderHook( + () => + client.useQuery( + "get", + "/string-array", + {}, + { + select: (data) => ({ + originalData: data, + customData: 1, + }), + }, + ), + { + wrapper, + }, + ); + + const { data } = result.current; + + expectTypeOf(data).toEqualTypeOf< + | { + originalData: string[]; + customData: number; + } + | undefined + >(); + }); + it("passes abort signal to fetch", async () => { let signalPassedToFetch: AbortSignal | undefined;