Skip to content

Commit a76801c

Browse files
authored
Infer Return Type for select from useInfiniteQuery (#2169)
* Infer select return from useInfiniteQuery * Include useInfiniteQuery in docs and update * Add type test for selects on useInfiniteQuery * Add changeset * Fix linter errors * Whoops, bad rebase, fixed
1 parent feb13e3 commit a76801c

File tree

5 files changed

+76
-2
lines changed

5 files changed

+76
-2
lines changed

.changeset/seven-monkeys-fetch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"openapi-react-query": minor
3+
---
4+
5+
[#2169](https://github.com/openapi-ts/openapi-typescript/pull/2169): Infer returned `data` type from `select` option when used with the `useInfiniteQuery` method.

docs/.vitepress/en.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export default defineConfig({
7474
{ text: "useQuery", link: "/use-query" },
7575
{ text: "useMutation", link: "/use-mutation" },
7676
{ text: "useSuspenseQuery", link: "/use-suspense-query" },
77+
{ text: "useInfiniteQuery", link: "/use-infinite-query" },
7778
{ text: "queryOptions", link: "/query-options" },
7879
],
7980
},

docs/openapi-react-query/use-infinite-query.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ const query = $api.useInfiniteQuery(
105105
- Only required if the OpenApi schema requires parameters.
106106
- The options `params` are used as key. See [Query Keys](https://tanstack.com/query/latest/docs/framework/react/guides/query-keys) for more information.
107107
- `infiniteQueryOptions`
108+
- `pageParamName` The query param name used for pagination, `"cursor"` by default.
108109
- The original `useInfiniteQuery` options.
109110
- [See more information](https://tanstack.com/query/latest/docs/framework/react/reference/useInfiniteQuery)
110111
- `queryClient`

packages/openapi-react-query/src/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export type UseInfiniteQueryMethod<Paths extends Record<string, Record<HttpMetho
110110
UseInfiniteQueryOptions<
111111
Response["data"],
112112
Response["error"],
113-
InfiniteData<Response["data"]>,
113+
InferSelectReturnType<InfiniteData<Response["data"]>, Options["select"]>,
114114
Response["data"],
115115
QueryKey<Paths, Method, Path>,
116116
unknown
@@ -125,7 +125,10 @@ export type UseInfiniteQueryMethod<Paths extends Record<string, Record<HttpMetho
125125
init: InitWithUnknowns<Init>,
126126
options: Options,
127127
queryClient?: QueryClient,
128-
) => UseInfiniteQueryResult<InfiniteData<Response["data"]>, Response["error"]>;
128+
) => UseInfiniteQueryResult<
129+
InferSelectReturnType<InfiniteData<Response["data"]>, Options["select"]>,
130+
Response["error"]
131+
>;
129132

130133
export type UseSuspenseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
131134
Method extends HttpMethod,

packages/openapi-react-query/test/index.test.tsx

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,5 +1087,69 @@ describe("client", () => {
10871087
const allItems = result.current.data?.pages.flatMap((page) => page.items);
10881088
expect(allItems).toEqual([1, 2, 3, 4, 5, 6]);
10891089
});
1090+
it("should use return type from select option", async () => {
1091+
const fetchClient = createFetchClient<paths>({ baseUrl });
1092+
const client = createClient(fetchClient);
1093+
1094+
// First page request handler
1095+
const firstRequestHandler = useMockRequestHandler({
1096+
baseUrl,
1097+
method: "get",
1098+
path: "/paginated-data",
1099+
status: 200,
1100+
body: { items: [1, 2, 3], nextPage: 1 },
1101+
});
1102+
1103+
const { result, rerender } = renderHook(
1104+
() =>
1105+
client.useInfiniteQuery(
1106+
"get",
1107+
"/paginated-data",
1108+
{
1109+
params: {
1110+
query: {
1111+
limit: 3,
1112+
},
1113+
},
1114+
},
1115+
{
1116+
getNextPageParam: (lastPage) => lastPage.nextPage,
1117+
initialPageParam: 0,
1118+
select: (data) => data.pages.flatMap((page) => page.items).filter((item) => item !== undefined),
1119+
},
1120+
),
1121+
{ wrapper },
1122+
);
1123+
1124+
// Wait for initial query to complete
1125+
await waitFor(() => expect(result.current.isSuccess).toBe(true));
1126+
1127+
expectTypeOf(result.current.data).toEqualTypeOf<number[] | undefined>();
1128+
expect(result.current.data).toEqual([1, 2, 3]);
1129+
1130+
// Set up mock for second page before triggering next page fetch
1131+
const secondRequestHandler = useMockRequestHandler({
1132+
baseUrl,
1133+
method: "get",
1134+
path: "/paginated-data",
1135+
status: 200,
1136+
body: { items: [4, 5, 6], nextPage: 2 },
1137+
});
1138+
1139+
// Fetch next page
1140+
await act(async () => {
1141+
await result.current.fetchNextPage();
1142+
// Force a rerender to ensure state is updated
1143+
rerender();
1144+
});
1145+
1146+
// Wait for second page to be fetched and verify loading states
1147+
await waitFor(() => {
1148+
expect(result.current.isFetching).toBe(false);
1149+
expect(result.current.hasNextPage).toBe(true);
1150+
});
1151+
1152+
expect(result.current.data).toEqual([1, 2, 3, 4, 5, 6]);
1153+
});
10901154
});
10911155
});

0 commit comments

Comments
 (0)