From 0f01fbd8168dabdb8179e2d85e4b7925743d7dd2 Mon Sep 17 00:00:00 2001 From: Ousama Ben Younes Date: Sat, 25 Apr 2026 10:50:01 +0000 Subject: [PATCH 1/3] fix(solid-query): allow combine to return arbitrary object shape (#7522) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Relax `TCombinedResult` from `extends QueriesResults` to `extends object` on `useQueries` / `createQueries`, mirroring the React adapter's flexibility. A `combine` callback can now return any object literal — e.g. `combine: (results) => ({ data: results.every(r => r.data) })` — instead of being forced to match the array of per-query results. The proxy/resource logic that follows still needs an array view of the store, so `state` is aliased to `Array` for those internal operations only. The reactive Solid store is preserved at runtime; the cast only affects TypeScript. Co-Authored-By: Claude --- ...solid-query-combine-custom-result-shape.md | 5 +++ .../src/__tests__/useQueries.test-d.tsx | 35 +++++++++++++++++++ packages/solid-query/src/useQueries.ts | 18 ++++++---- 3 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 .changeset/fix-solid-query-combine-custom-result-shape.md diff --git a/.changeset/fix-solid-query-combine-custom-result-shape.md b/.changeset/fix-solid-query-combine-custom-result-shape.md new file mode 100644 index 00000000000..f1670c0b389 --- /dev/null +++ b/.changeset/fix-solid-query-combine-custom-result-shape.md @@ -0,0 +1,5 @@ +--- +'@tanstack/solid-query': patch +--- + +fix(solid-query): allow `combine` to return a custom result shape from `useQueries` / `createQueries`, matching the behavior of the React adapter. Relaxed the `TCombinedResult` generic from `extends QueriesResults` to `extends object`, so a `combine` callback can return any object literal (e.g. `{ data: boolean }`) instead of being constrained to the array of per-query results (#7522). diff --git a/packages/solid-query/src/__tests__/useQueries.test-d.tsx b/packages/solid-query/src/__tests__/useQueries.test-d.tsx index 3567f77bfc6..938483fa4ef 100644 --- a/packages/solid-query/src/__tests__/useQueries.test-d.tsx +++ b/packages/solid-query/src/__tests__/useQueries.test-d.tsx @@ -289,4 +289,39 @@ describe('useQueries', () => { }, })) }) + + it('combine should allow returning an arbitrary object shape (#7522)', () => { + const result = useQueries(() => ({ + queries: [ + { + queryKey: queryKey(), + queryFn: () => Promise.resolve(true), + }, + { + queryKey: queryKey(), + queryFn: () => Promise.resolve(false), + }, + ], + combine: (results) => ({ + data: results.every((r) => r.data), + }), + })) + + expectTypeOf(result).toEqualTypeOf<{ data: boolean }>() + }) + + it('combine should infer custom object shape from .map() queries (#7522)', () => { + const list = ['test1', 'test2'] + const result = useQueries(() => ({ + queries: list.map((key) => ({ + queryKey: ['key', key], + queryFn: () => true, + })), + combine: (results) => ({ + data: results.every((r) => r.data), + }), + })) + + expectTypeOf(result).toEqualTypeOf<{ data: boolean }>() + }) }) diff --git a/packages/solid-query/src/useQueries.ts b/packages/solid-query/src/useQueries.ts index 1e5592775db..0cb6861306b 100644 --- a/packages/solid-query/src/useQueries.ts +++ b/packages/solid-query/src/useQueries.ts @@ -186,7 +186,7 @@ type QueriesResults< export function useQueries< T extends Array, - TCombinedResult extends QueriesResults = QueriesResults, + TCombinedResult extends object = QueriesResults, >( queriesOptions: Accessor<{ queries: @@ -243,11 +243,17 @@ export function useQueries< ), ) + // Internal "view" of the underlying per-query results. `state` is typed as + // `TCombinedResult` (which may be a user-defined non-array shape via `combine`), + // but the proxy/resource logic below treats it as an array of `QueryObserverResult`. + // The cast preserves Solid's reactive store at runtime — it only affects TypeScript. + const queryResults = state as unknown as Array + const dataResources = createMemo( on( - () => state.length, + () => queryResults.length, () => - state.map((queryRes) => { + queryResults.map((queryRes) => { const dataPromise = () => new Promise((resolve) => { if (queryRes.isFetching && queryRes.isLoading) return @@ -262,7 +268,7 @@ export function useQueries< const dataResources_ = dataResources() for (let index = 0; index < dataResources_.length; index++) { const dataResource = dataResources_[index]! - dataResource[1].mutate(() => unwrap(state[index]!.data)) + dataResource[1].mutate(() => unwrap(queryResults[index]!.data)) dataResource[1].refetch() } }) @@ -278,7 +284,7 @@ export function useQueries< const unwrappedResult = { ...unwrap(result[index]) } // @ts-expect-error typescript pedantry regarding the possible range of index setState(index, unwrap(unwrappedResult)) - dataResource[1].mutate(() => unwrap(state[index]!.data)) + dataResource[1].mutate(() => unwrap(queryResults[index]!.data)) dataResource[1].refetch() } }) @@ -332,7 +338,7 @@ export function useQueries< }) const getProxies = () => - state.map((s, index) => { + queryResults.map((s, index) => { return new Proxy(s, handler(index)) }) From d1df126685a258f5fc334909df340d3d7bd030e8 Mon Sep 17 00:00:00 2001 From: Ousama Ben Younes Date: Sat, 25 Apr 2026 22:35:30 +0000 Subject: [PATCH 2/3] test(solid-query): add runtime test for combine returning arbitrary shape Type-only tests (`expectTypeOf(result).toEqualTypeOf<{ data: boolean }>()`) silently pass even when `useQueries` mishandles the combined object at runtime. This adds a render-based test that mounts the hook with a non-array combine return and asserts the resolved shape after queries settle, so a regression in `useQueries.ts` (e.g. proxy/array assumptions) fails the suite instead of slipping through. --- .../src/__tests__/useQueries.test.tsx | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/packages/solid-query/src/__tests__/useQueries.test.tsx b/packages/solid-query/src/__tests__/useQueries.test.tsx index 2f8e61a7402..e64c7152861 100644 --- a/packages/solid-query/src/__tests__/useQueries.test.tsx +++ b/packages/solid-query/src/__tests__/useQueries.test.tsx @@ -732,4 +732,41 @@ describe('useQueries', () => { await vi.advanceTimersByTimeAsync(20) QueriesObserverSpy.mockRestore() }) + + it('combine should resolve at runtime when returning an arbitrary object shape (#7522)', async () => { + const key1 = queryKey() + const key2 = queryKey() + + function Page() { + const result = useQueries(() => ({ + queries: [ + { + queryKey: key1, + queryFn: () => sleep(10).then(() => true), + }, + { + queryKey: key2, + queryFn: () => sleep(10).then(() => false), + }, + ], + combine: (results) => ({ + allTrue: results.every((r) => r.data === true), + }), + })) + + return
allTrue: {String(result.allTrue)}
+ } + + const rendered = render(() => ( + + + + )) + + expect(rendered.getByText('allTrue: false')).toBeInTheDocument() + + await vi.advanceTimersByTimeAsync(20) + + expect(rendered.getByText('allTrue: false')).toBeInTheDocument() + }) }) From e110aff2b05c1f467198e6c5934ffc1c72ca742f Mon Sep 17 00:00:00 2001 From: Ousama Ben Younes Date: Sun, 26 Apr 2026 07:24:38 +0000 Subject: [PATCH 3/3] Revert "test(solid-query): add runtime test for combine returning arbitrary shape" This reverts commit d1df126685a258f5fc334909df340d3d7bd030e8. --- .../src/__tests__/useQueries.test.tsx | 37 ------------------- 1 file changed, 37 deletions(-) diff --git a/packages/solid-query/src/__tests__/useQueries.test.tsx b/packages/solid-query/src/__tests__/useQueries.test.tsx index e64c7152861..2f8e61a7402 100644 --- a/packages/solid-query/src/__tests__/useQueries.test.tsx +++ b/packages/solid-query/src/__tests__/useQueries.test.tsx @@ -732,41 +732,4 @@ describe('useQueries', () => { await vi.advanceTimersByTimeAsync(20) QueriesObserverSpy.mockRestore() }) - - it('combine should resolve at runtime when returning an arbitrary object shape (#7522)', async () => { - const key1 = queryKey() - const key2 = queryKey() - - function Page() { - const result = useQueries(() => ({ - queries: [ - { - queryKey: key1, - queryFn: () => sleep(10).then(() => true), - }, - { - queryKey: key2, - queryFn: () => sleep(10).then(() => false), - }, - ], - combine: (results) => ({ - allTrue: results.every((r) => r.data === true), - }), - })) - - return
allTrue: {String(result.allTrue)}
- } - - const rendered = render(() => ( - - - - )) - - expect(rendered.getByText('allTrue: false')).toBeInTheDocument() - - await vi.advanceTimersByTimeAsync(20) - - expect(rendered.getByText('allTrue: false')).toBeInTheDocument() - }) })