From 594a3dd164e5e7980f9585ffa94c88a7040d881c Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Tue, 13 Jan 2026 12:27:34 +0900 Subject: [PATCH] Add useQueries --- .../changepack_log_WFMzIozQ08ypLxNWRk6Bk.json | 1 + .../src/__tests__/query-client.test.ts | 6 + .../src/__tests__/query-client.test.tsx | 133 ++++++++++++++++++ packages/react-query/src/query-client.ts | 80 +++++++++++ 4 files changed, 220 insertions(+) create mode 100644 .changepacks/changepack_log_WFMzIozQ08ypLxNWRk6Bk.json diff --git a/.changepacks/changepack_log_WFMzIozQ08ypLxNWRk6Bk.json b/.changepacks/changepack_log_WFMzIozQ08ypLxNWRk6Bk.json new file mode 100644 index 0000000..a40fef6 --- /dev/null +++ b/.changepacks/changepack_log_WFMzIozQ08ypLxNWRk6Bk.json @@ -0,0 +1 @@ +{"changes":{"packages/react-query/package.json":"Patch"},"note":"Add useQueries","date":"2026-01-13T03:27:15.760011900Z"} \ No newline at end of file diff --git a/packages/react-query/src/__tests__/query-client.test.ts b/packages/react-query/src/__tests__/query-client.test.ts index 7d4dc54..b12ee2d 100644 --- a/packages/react-query/src/__tests__/query-client.test.ts +++ b/packages/react-query/src/__tests__/query-client.test.ts @@ -33,6 +33,12 @@ test('DevupQueryClient useInfiniteQuery method exists', () => { expect(typeof queryClient.useInfiniteQuery).toBe('function') }) +test('DevupQueryClient useQueries method exists', () => { + const api = createApi({ baseUrl: 'https://api.example.com' }) + const queryClient = new DevupQueryClient(api) + expect(typeof queryClient.useQueries).toBe('function') +}) + test('getQueryKey returns correct key without options', () => { const result = getQueryKey('get', '/test', undefined) expect(result).toEqual(['get', '/test']) diff --git a/packages/react-query/src/__tests__/query-client.test.tsx b/packages/react-query/src/__tests__/query-client.test.tsx index 0311d81..11ef928 100644 --- a/packages/react-query/src/__tests__/query-client.test.tsx +++ b/packages/react-query/src/__tests__/query-client.test.tsx @@ -355,3 +355,136 @@ test('DevupQueryClient useInfiniteQuery with different HTTP methods', async () = }) } }) + +test('DevupQueryClient useQueries with multiple queries', async () => { + const api = createApi({ baseUrl: 'https://api.example.com' }) + const queryClient = new DevupQueryClient(api) + + const { result } = renderHook( + () => + queryClient.useQueries([ + ['get', '/test1'], + ['get', '/test2'], + ]), + { wrapper: createWrapper() }, + ) + + await waitFor( + () => { + expect(result.current.every((r) => r.isSuccess)).toBe(true) + }, + { timeout: 5000 }, + ) + + expect(result.current).toHaveLength(2) + expect(result.current[0].data).toEqual({ id: 1, name: 'test' }) + expect(result.current[1].data).toEqual({ id: 1, name: 'test' }) +}) + +test('DevupQueryClient useQueries with options', async () => { + const api = createApi({ baseUrl: 'https://api.example.com' }) + const queryClient = new DevupQueryClient(api) + + const { result } = renderHook( + () => + queryClient.useQueries([ + ['get', '/test' as any, { params: { id: '123' } }], + ]), + { wrapper: createWrapper() }, + ) + + await waitFor( + () => { + expect(result.current[0].isSuccess).toBe(true) + }, + { timeout: 5000 }, + ) + + expect(result.current[0].data).toEqual({ id: 1, name: 'test' }) +}) + +test('DevupQueryClient useQueries with combine', async () => { + const api = createApi({ baseUrl: 'https://api.example.com' }) + const queryClient = new DevupQueryClient(api) + + const { result } = renderHook( + () => + queryClient.useQueries( + [ + ['get', '/test1' as any], + ['get', '/test2' as any], + ], + { + combine: (results) => ({ + data: results.map((r) => r.data), + pending: results.some((r) => r.isPending), + }), + }, + ), + { wrapper: createWrapper() }, + ) + + await waitFor( + () => { + expect(result.current.pending).toBe(false) + }, + { timeout: 5000 }, + ) + + expect(result.current.data).toEqual([ + { id: 1, name: 'test' }, + { id: 1, name: 'test' }, + ]) +}) + +test('DevupQueryClient useQueries with different HTTP methods', async () => { + const api = createApi({ baseUrl: 'https://api.example.com' }) + const queryClient = new DevupQueryClient(api) + + const { result } = renderHook( + () => + queryClient.useQueries([ + ['get', '/test' as any], + ['GET', '/test' as any], + ['post', '/test' as any], + ]), + { wrapper: createWrapper() }, + ) + + await waitFor( + () => { + expect(result.current.every((r) => r.isSuccess)).toBe(true) + }, + { timeout: 5000 }, + ) + + expect(result.current).toHaveLength(3) +}) + +test('DevupQueryClient useQueries with queryOptions', async () => { + const api = createApi({ baseUrl: 'https://api.example.com' }) + const queryClient = new DevupQueryClient(api) + + const { result } = renderHook( + () => + queryClient.useQueries([ + ['get', '/test' as any, undefined, { staleTime: 1000 }], + [ + 'get', + '/test2' as any, + { params: { id: '123' } }, + { staleTime: 2000 }, + ], + ]), + { wrapper: createWrapper() }, + ) + + await waitFor( + () => { + expect(result.current.every((r) => r.isSuccess)).toBe(true) + }, + { timeout: 5000 }, + ) + + expect(result.current).toHaveLength(2) +}) diff --git a/packages/react-query/src/query-client.ts b/packages/react-query/src/query-client.ts index 67441d8..d97273d 100644 --- a/packages/react-query/src/query-client.ts +++ b/packages/react-query/src/query-client.ts @@ -17,6 +17,7 @@ import type { import { useInfiniteQuery, useMutation, + useQueries, useQuery, useSuspenseQuery, } from '@tanstack/react-query' @@ -299,4 +300,83 @@ export class DevupQueryClient> { options[2], ) } + + useQueries< + M extends + | 'get' + | 'post' + | 'put' + | 'delete' + | 'patch' + | 'GET' + | 'POST' + | 'PUT' + | 'DELETE' + | 'PATCH', + ST extends { + get: DevupGetApiStructScope + post: DevupPostApiStructScope + put: DevupPutApiStructScope + delete: DevupDeleteApiStructScope + patch: DevupPatchApiStructScope + GET: DevupGetApiStructScope + POST: DevupPostApiStructScope + PUT: DevupPutApiStructScope + DELETE: DevupDeleteApiStructScope + PATCH: DevupPatchApiStructScope + }[M], + T extends ConditionalKeys, + O extends Additional, + D extends ExtractValue, + E extends ExtractValue, + TCombinedResult = Array>>, + >( + queries: Array< + [ + method: M, + path: T, + options?: ConditionalApiOption, + queryOptions?: Omit< + Parameters>[0], + 'queryFn' | 'queryKey' + >, + ] + >, + options?: { + combine?: ( + results: Array>>, + ) => TCombinedResult + queryClient?: Parameters[1] + }, + ): TCombinedResult { + return useQueries( + { + queries: queries.map(([method, path, apiOptions, queryOptions]) => ({ + queryKey: getQueryKey(method, path, apiOptions), + queryFn: ({ + queryKey: [methodKey, pathKey, ...restOptions], + signal, + }: { + queryKey: [M, T, ...unknown[]] + signal: AbortSignal + }): Promise => + // biome-ignore lint/suspicious/noExplicitAny: can't use method as a function + (this.api as any) + [methodKey as string](pathKey, { + signal, + ...(restOptions[0] as DevupApiRequestInit), + }) + .then(({ data, error }: DevupApiResponse) => { + if (error) throw error + return data + }), + ...queryOptions, + })) as Parameters[0]['queries'], + combine: options?.combine as Parameters< + typeof useQueries + >[0]['combine'], + }, + options?.queryClient, + ) as TCombinedResult + } }