Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/silver-coins-mix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@tanstack/angular-query-experimental': minor
'@tanstack/svelte-query': minor
'@tanstack/react-query': minor
'@tanstack/solid-query': minor
'@tanstack/query-core': minor
'@tanstack/vue-query': minor
---

feat(query-core): Allow disabling structuralSharing for useQueries.
5 changes: 4 additions & 1 deletion docs/framework/react/reference/useQueries.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ The `useQueries` hook accepts an options object with a **queries** key whose val
- Use this to provide a custom QueryClient. Otherwise, the one from the nearest context will be used.
- `combine?: (result: UseQueriesResults) => TCombinedResult`
- Use this to combine the results of the queries into a single value.
- `structuralSharing?: boolean`
- Set this to `false` to disable structural sharing between query results when `combine` is provided.
- Defaults to `true`.

> Having the same query key more than once in the array of query objects may cause some data to be shared between queries. To avoid this, consider de-duplicating the queries and map the results back to the desired structure.

Expand All @@ -37,7 +40,7 @@ The `useQueries` hook returns an array with all the query results. The order ret

## Combine

If you want to combine `data` (or other Query information) from the results into a single value, you can use the `combine` option. The result will be structurally shared to be as referentially stable as possible.
If you want to combine `data` (or other Query information) from the results into a single value, you can use the `combine` option. The result will be structurally shared to be as referentially stable as possible. If you want to disable structural sharing for the combined result, you can set the `structuralSharing` option to `false`.

```tsx
const ids = [1, 2, 3]
Expand Down
8 changes: 8 additions & 0 deletions packages/angular-query-experimental/src/inject-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,12 @@ export interface InjectQueriesOptions<
...{ [K in keyof T]: GetCreateQueryOptionsForCreateQueries<T[K]> },
]
combine?: (result: QueriesResults<T>) => TCombinedResult
/**
* Set this to `false` to disable structural sharing between query results.
* Only applies when `combine` is provided.
* Defaults to `true`.
*/
structuralSharing?: boolean
}

/**
Expand Down Expand Up @@ -271,6 +277,8 @@ export function injectQueries<
observerSignal().getOptimisticResult(
defaultedQueries(),
(optionsSignal() as QueriesObserverOptions<TCombinedResult>).combine,
(optionsSignal() as QueriesObserverOptions<TCombinedResult>)
.structuralSharing,
),
)

Expand Down
7 changes: 7 additions & 0 deletions packages/query-core/src/__tests__/queriesObserver.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ describe('queriesObserver', () => {
{ queryKey: key1, queryFn: queryFn1 },
],
undefined,
undefined,
)[0],
)

Expand Down Expand Up @@ -414,6 +415,7 @@ describe('queriesObserver', () => {
const [initialRaw, getInitialCombined] = observer.getOptimisticResult(
[{ queryKey: key1, queryFn: queryFn1 }],
combine,
undefined,
)
const initialCombined = getInitialCombined(initialRaw)

Expand All @@ -426,6 +428,7 @@ describe('queriesObserver', () => {
const [newRaw, getNewCombined] = observer.getOptimisticResult(
newQueries,
combine,
undefined,
)
const newCombined = getNewCombined(newRaw)

Expand Down Expand Up @@ -461,6 +464,7 @@ describe('queriesObserver', () => {
{ queryKey: key2, queryFn: queryFn2 },
],
combine,
undefined,
)
const initialCombined = getInitialCombined(initialRaw)

Expand All @@ -470,6 +474,7 @@ describe('queriesObserver', () => {
const [newRaw, getNewCombined] = observer.getOptimisticResult(
newQueries,
combine,
undefined,
)
const newCombined = getNewCombined(newRaw)

Expand Down Expand Up @@ -497,6 +502,7 @@ describe('queriesObserver', () => {
const [initialRaw, getInitialCombined] = observer.getOptimisticResult(
[{ queryKey: key1, queryFn: queryFn1 }],
combine,
undefined,
)
const initialCombined = getInitialCombined(initialRaw)

Expand All @@ -505,6 +511,7 @@ describe('queriesObserver', () => {
const [newRaw, getNewCombined] = observer.getOptimisticResult(
[{ queryKey: key2, queryFn: queryFn2 }],
combine,
undefined,
)
const newCombined = getNewCombined(newRaw)

Expand Down
31 changes: 25 additions & 6 deletions packages/query-core/src/queriesObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ export interface QueriesObserverOptions<
TCombinedResult = Array<QueryObserverResult>,
> {
combine?: CombineFn<TCombinedResult>
/**
* Set this to `false` to disable structural sharing between query results.
* Only applies when `combine` is provided.
* Defaults to `true`.
*/
structuralSharing?: boolean
}

export class QueriesObserver<
Expand Down Expand Up @@ -172,6 +178,7 @@ export class QueriesObserver<
getOptimisticResult(
queries: Array<QueryObserverOptions>,
combine: CombineFn<TCombinedResult> | undefined,
structuralSharing: boolean | undefined,
): [
rawResult: Array<QueryObserverResult>,
combineResult: (r?: Array<QueryObserverResult>) => TCombinedResult,
Expand All @@ -188,7 +195,12 @@ export class QueriesObserver<
return [
result,
(r?: Array<QueryObserverResult>) => {
return this.#combineResult(r ?? result, combine, queryHashes)
return this.#combineResult(
r ?? result,
combine,
structuralSharing,
queryHashes,
)
},
() => {
return this.#trackResult(result, matches)
Expand Down Expand Up @@ -216,6 +228,7 @@ export class QueriesObserver<
#combineResult(
input: Array<QueryObserverResult>,
combine: CombineFn<TCombinedResult> | undefined,
structuralSharing: boolean | undefined = true,
queryHashes?: Array<string>,
): TCombinedResult {
if (combine) {
Expand All @@ -238,10 +251,12 @@ export class QueriesObserver<
if (queryHashes !== undefined) {
this.#lastQueryHashes = queryHashes
}
this.#combinedResult = replaceEqualDeep(
this.#combinedResult,
combine(input),
)

const combined = combine(input)

this.#combinedResult = structuralSharing
? replaceEqualDeep(this.#combinedResult, combined)
: combined
}

return this.#combinedResult
Expand Down Expand Up @@ -296,7 +311,11 @@ export class QueriesObserver<
if (this.hasListeners()) {
const previousResult = this.#combinedResult
const newTracked = this.#trackResult(this.#result, this.#observerMatches)
const newResult = this.#combineResult(newTracked, this.#options?.combine)
const newResult = this.#combineResult(
newTracked,
this.#options?.combine,
this.#options?.structuralSharing,
)

if (previousResult !== newResult) {
notifyManager.batch(() => {
Expand Down
7 changes: 7 additions & 0 deletions packages/react-query/src/useQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,12 @@ export function useQueries<
| readonly [...QueriesOptions<T>]
| readonly [...{ [K in keyof T]: GetUseQueryOptionsForUseQueries<T[K]> }]
combine?: (result: QueriesResults<T>) => TCombinedResult
/**
* Set this to `false` to disable structural sharing between query results.
* Only applies when `combine` is provided.
* Defaults to `true`.
*/
structuralSharing?: boolean
subscribed?: boolean
},
queryClient?: QueryClient,
Expand Down Expand Up @@ -264,6 +270,7 @@ export function useQueries<
observer.getOptimisticResult(
defaultedQueries,
(options as QueriesObserverOptions<TCombinedResult>).combine,
options.structuralSharing,
)

const shouldSubscribe = !isRestoring && options.subscribed !== false
Expand Down
23 changes: 13 additions & 10 deletions packages/solid-query/src/useQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,12 @@ export function useQueries<
| readonly [...QueriesOptions<T>]
| readonly [...{ [K in keyof T]: GetOptions<T[K]> }]
combine?: (result: QueriesResults<T>) => TCombinedResult
/**
* Set this to `false` to disable structural sharing between query results.
* Only applies when `combine` is provided.
* Defaults to `true`.
*/
structuralSharing?: boolean
}>,
queryClient?: Accessor<QueryClient>,
): TCombinedResult {
Expand All @@ -218,6 +224,7 @@ export function useQueries<
queriesOptions().combine
? ({
combine: queriesOptions().combine,
structuralSharing: queriesOptions().structuralSharing,
} as QueriesObserverOptions<TCombinedResult>)
: undefined,
)
Expand All @@ -226,6 +233,8 @@ export function useQueries<
observer.getOptimisticResult(
defaultedQueries(),
(queriesOptions() as QueriesObserverOptions<TCombinedResult>).combine,
(queriesOptions() as QueriesObserverOptions<TCombinedResult>)
.structuralSharing,
)[1](),
)

Expand All @@ -238,6 +247,8 @@ export function useQueries<
defaultedQueries(),
(queriesOptions() as QueriesObserverOptions<TCombinedResult>)
.combine,
(queriesOptions() as QueriesObserverOptions<TCombinedResult>)
.structuralSharing,
)[1](),
),
),
Expand Down Expand Up @@ -303,22 +314,14 @@ export function useQueries<
onMount(() => {
observer.setQueries(
defaultedQueries(),
queriesOptions().combine
? ({
combine: queriesOptions().combine,
} as QueriesObserverOptions<TCombinedResult>)
: undefined,
queriesOptions() as QueriesObserverOptions<TCombinedResult>,
)
})

createComputed(() => {
observer.setQueries(
defaultedQueries(),
queriesOptions().combine
? ({
combine: queriesOptions().combine,
} as QueriesObserverOptions<TCombinedResult>)
: undefined,
queriesOptions() as QueriesObserverOptions<TCombinedResult>,
)
})

Expand Down
21 changes: 15 additions & 6 deletions packages/svelte-query/src/createQueries.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,13 +197,20 @@ export function createQueries<
...{ [K in keyof T]: GetCreateQueryOptionsForCreateQueries<T[K]> },
]
combine?: (result: QueriesResults<T>) => TCombinedResult
/**
* Set this to `false` to disable structural sharing between query results.
* Only applies when `combine` is provided.
* Defaults to `true`.
*/
structuralSharing?: boolean
}>,
queryClient?: Accessor<QueryClient>,
): TCombinedResult {
const client = $derived(useQueryClient(queryClient?.()))
const isRestoring = useIsRestoring()

const { queries, combine } = $derived.by(createQueriesOptions)
const { queries, ...derivedCreateQueriesOptions } =
$derived.by(createQueriesOptions)
const resolvedQueryOptions = $derived(
queries.map((opts) => {
const resolvedOptions = client.defaultQueryOptions(opts)
Expand All @@ -220,14 +227,15 @@ export function createQueries<
new QueriesObserver<TCombinedResult>(
client,
resolvedQueryOptions,
combine as QueriesObserverOptions<TCombinedResult>,
derivedCreateQueriesOptions as QueriesObserverOptions<TCombinedResult>,
),
)

function createResult() {
const [_, getCombinedResult, trackResult] = observer.getOptimisticResult(
resolvedQueryOptions,
combine as QueriesObserverOptions<TCombinedResult>['combine'],
derivedCreateQueriesOptions.combine as QueriesObserverOptions<TCombinedResult>['combine'],
derivedCreateQueriesOptions.structuralSharing,
)
return getCombinedResult(trackResult())
}
Expand All @@ -244,9 +252,10 @@ export function createQueries<
})

$effect.pre(() => {
observer.setQueries(resolvedQueryOptions, {
combine,
} as QueriesObserverOptions<TCombinedResult>)
observer.setQueries(
resolvedQueryOptions,
derivedCreateQueriesOptions as QueriesObserverOptions<TCombinedResult>,
)
update(createResult())
})

Expand Down
8 changes: 8 additions & 0 deletions packages/vue-query/src/useQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,12 @@ export function useQueries<
]
>
combine?: (result: UseQueriesResults<T>) => TCombinedResult
/**
* Set this to `false` to disable structural sharing between query results.
* Only applies when `combine` is provided.
* Defaults to `true`.
*/
structuralSharing?: boolean
},
queryClient?: QueryClient,
): Readonly<Ref<TCombinedResult>> {
Expand Down Expand Up @@ -296,6 +302,7 @@ export function useQueries<
const [results, getCombinedResult] = observer.getOptimisticResult(
defaultedQueries.value,
(options as QueriesObserverOptions<TCombinedResult>).combine,
options.structuralSharing,
)

return getCombinedResult(
Expand All @@ -306,6 +313,7 @@ export function useQueries<
const [{ [index]: query }] = observer.getOptimisticResult(
defaultedQueries.value,
(options as QueriesObserverOptions<TCombinedResult>).combine,
options.structuralSharing,
)

return query!.refetch(...args)
Expand Down