Skip to content

Commit 441edb9

Browse files
authored
Merge pull request #39 from dev-five-git/use-queries
Add useQueries
2 parents f829750 + 594a3dd commit 441edb9

File tree

4 files changed

+220
-0
lines changed

4 files changed

+220
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"changes":{"packages/react-query/package.json":"Patch"},"note":"Add useQueries","date":"2026-01-13T03:27:15.760011900Z"}

packages/react-query/src/__tests__/query-client.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ test('DevupQueryClient useInfiniteQuery method exists', () => {
3333
expect(typeof queryClient.useInfiniteQuery).toBe('function')
3434
})
3535

36+
test('DevupQueryClient useQueries method exists', () => {
37+
const api = createApi({ baseUrl: 'https://api.example.com' })
38+
const queryClient = new DevupQueryClient(api)
39+
expect(typeof queryClient.useQueries).toBe('function')
40+
})
41+
3642
test('getQueryKey returns correct key without options', () => {
3743
const result = getQueryKey('get', '/test', undefined)
3844
expect(result).toEqual(['get', '/test'])

packages/react-query/src/__tests__/query-client.test.tsx

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,3 +355,136 @@ test('DevupQueryClient useInfiniteQuery with different HTTP methods', async () =
355355
})
356356
}
357357
})
358+
359+
test('DevupQueryClient useQueries with multiple queries', async () => {
360+
const api = createApi({ baseUrl: 'https://api.example.com' })
361+
const queryClient = new DevupQueryClient(api)
362+
363+
const { result } = renderHook(
364+
() =>
365+
queryClient.useQueries([
366+
['get', '/test1'],
367+
['get', '/test2'],
368+
]),
369+
{ wrapper: createWrapper() },
370+
)
371+
372+
await waitFor(
373+
() => {
374+
expect(result.current.every((r) => r.isSuccess)).toBe(true)
375+
},
376+
{ timeout: 5000 },
377+
)
378+
379+
expect(result.current).toHaveLength(2)
380+
expect(result.current[0].data).toEqual({ id: 1, name: 'test' })
381+
expect(result.current[1].data).toEqual({ id: 1, name: 'test' })
382+
})
383+
384+
test('DevupQueryClient useQueries with options', async () => {
385+
const api = createApi({ baseUrl: 'https://api.example.com' })
386+
const queryClient = new DevupQueryClient(api)
387+
388+
const { result } = renderHook(
389+
() =>
390+
queryClient.useQueries([
391+
['get', '/test' as any, { params: { id: '123' } }],
392+
]),
393+
{ wrapper: createWrapper() },
394+
)
395+
396+
await waitFor(
397+
() => {
398+
expect(result.current[0].isSuccess).toBe(true)
399+
},
400+
{ timeout: 5000 },
401+
)
402+
403+
expect(result.current[0].data).toEqual({ id: 1, name: 'test' })
404+
})
405+
406+
test('DevupQueryClient useQueries with combine', async () => {
407+
const api = createApi({ baseUrl: 'https://api.example.com' })
408+
const queryClient = new DevupQueryClient(api)
409+
410+
const { result } = renderHook(
411+
() =>
412+
queryClient.useQueries(
413+
[
414+
['get', '/test1' as any],
415+
['get', '/test2' as any],
416+
],
417+
{
418+
combine: (results) => ({
419+
data: results.map((r) => r.data),
420+
pending: results.some((r) => r.isPending),
421+
}),
422+
},
423+
),
424+
{ wrapper: createWrapper() },
425+
)
426+
427+
await waitFor(
428+
() => {
429+
expect(result.current.pending).toBe(false)
430+
},
431+
{ timeout: 5000 },
432+
)
433+
434+
expect(result.current.data).toEqual([
435+
{ id: 1, name: 'test' },
436+
{ id: 1, name: 'test' },
437+
])
438+
})
439+
440+
test('DevupQueryClient useQueries with different HTTP methods', async () => {
441+
const api = createApi({ baseUrl: 'https://api.example.com' })
442+
const queryClient = new DevupQueryClient(api)
443+
444+
const { result } = renderHook(
445+
() =>
446+
queryClient.useQueries([
447+
['get', '/test' as any],
448+
['GET', '/test' as any],
449+
['post', '/test' as any],
450+
]),
451+
{ wrapper: createWrapper() },
452+
)
453+
454+
await waitFor(
455+
() => {
456+
expect(result.current.every((r) => r.isSuccess)).toBe(true)
457+
},
458+
{ timeout: 5000 },
459+
)
460+
461+
expect(result.current).toHaveLength(3)
462+
})
463+
464+
test('DevupQueryClient useQueries with queryOptions', async () => {
465+
const api = createApi({ baseUrl: 'https://api.example.com' })
466+
const queryClient = new DevupQueryClient(api)
467+
468+
const { result } = renderHook(
469+
() =>
470+
queryClient.useQueries([
471+
['get', '/test' as any, undefined, { staleTime: 1000 }],
472+
[
473+
'get',
474+
'/test2' as any,
475+
{ params: { id: '123' } },
476+
{ staleTime: 2000 },
477+
],
478+
]),
479+
{ wrapper: createWrapper() },
480+
)
481+
482+
await waitFor(
483+
() => {
484+
expect(result.current.every((r) => r.isSuccess)).toBe(true)
485+
},
486+
{ timeout: 5000 },
487+
)
488+
489+
expect(result.current).toHaveLength(2)
490+
})

packages/react-query/src/query-client.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type {
1717
import {
1818
useInfiniteQuery,
1919
useMutation,
20+
useQueries,
2021
useQuery,
2122
useSuspenseQuery,
2223
} from '@tanstack/react-query'
@@ -299,4 +300,83 @@ export class DevupQueryClient<S extends ConditionalKeys<DevupApiServers>> {
299300
options[2],
300301
)
301302
}
303+
304+
useQueries<
305+
M extends
306+
| 'get'
307+
| 'post'
308+
| 'put'
309+
| 'delete'
310+
| 'patch'
311+
| 'GET'
312+
| 'POST'
313+
| 'PUT'
314+
| 'DELETE'
315+
| 'PATCH',
316+
ST extends {
317+
get: DevupGetApiStructScope<S>
318+
post: DevupPostApiStructScope<S>
319+
put: DevupPutApiStructScope<S>
320+
delete: DevupDeleteApiStructScope<S>
321+
patch: DevupPatchApiStructScope<S>
322+
GET: DevupGetApiStructScope<S>
323+
POST: DevupPostApiStructScope<S>
324+
PUT: DevupPutApiStructScope<S>
325+
DELETE: DevupDeleteApiStructScope<S>
326+
PATCH: DevupPatchApiStructScope<S>
327+
}[M],
328+
T extends ConditionalKeys<ST>,
329+
O extends Additional<T, ST>,
330+
D extends ExtractValue<O, 'response'>,
331+
E extends ExtractValue<O, 'error'>,
332+
TCombinedResult = Array<ReturnType<typeof useQuery<D, E>>>,
333+
>(
334+
queries: Array<
335+
[
336+
method: M,
337+
path: T,
338+
options?: ConditionalApiOption<O>,
339+
queryOptions?: Omit<
340+
Parameters<typeof useQuery<D, E>>[0],
341+
'queryFn' | 'queryKey'
342+
>,
343+
]
344+
>,
345+
options?: {
346+
combine?: (
347+
results: Array<ReturnType<typeof useQuery<D, E>>>,
348+
) => TCombinedResult
349+
queryClient?: Parameters<typeof useQueries>[1]
350+
},
351+
): TCombinedResult {
352+
return useQueries(
353+
{
354+
queries: queries.map(([method, path, apiOptions, queryOptions]) => ({
355+
queryKey: getQueryKey(method, path, apiOptions),
356+
queryFn: ({
357+
queryKey: [methodKey, pathKey, ...restOptions],
358+
signal,
359+
}: {
360+
queryKey: [M, T, ...unknown[]]
361+
signal: AbortSignal
362+
}): Promise<D> =>
363+
// biome-ignore lint/suspicious/noExplicitAny: can't use method as a function
364+
(this.api as any)
365+
[methodKey as string](pathKey, {
366+
signal,
367+
...(restOptions[0] as DevupApiRequestInit),
368+
})
369+
.then(({ data, error }: DevupApiResponse<D, E>) => {
370+
if (error) throw error
371+
return data
372+
}),
373+
...queryOptions,
374+
})) as Parameters<typeof useQueries>[0]['queries'],
375+
combine: options?.combine as Parameters<
376+
typeof useQueries
377+
>[0]['combine'],
378+
},
379+
options?.queryClient,
380+
) as TCombinedResult
381+
}
302382
}

0 commit comments

Comments
 (0)