Skip to content

Commit 9e0bfa8

Browse files
authored
fix(queriesObserver): always use latest combine function (TanStack#6651)
* fix(queriesObserver): always use latest combine function computing the optimistic result didn't use the latest combine function, but the one set from setOptions (which runs in an effect, but is too late) * fix: don't forget about svelte
1 parent f43d2ad commit 9e0bfa8

File tree

7 files changed

+82
-11
lines changed

7 files changed

+82
-11
lines changed

packages/angular-query-experimental/src/inject-queries.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,10 @@ export function injectQueries<
236236
)
237237
})
238238

239-
const [, getCombinedResult] =
240-
observer.getOptimisticResult(defaultedQueries())
239+
const [, getCombinedResult] = observer.getOptimisticResult(
240+
defaultedQueries(),
241+
(options as QueriesObserverOptions<TCombinedResult>).combine,
242+
)
241243

242244
const result = signal(getCombinedResult() as any)
243245

packages/query-core/src/queriesObserver.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,14 @@ function replaceAt<T>(array: Array<T>, index: number, value: T): Array<T> {
2222

2323
type QueriesObserverListener = (result: Array<QueryObserverResult>) => void
2424

25+
type CombineFn<TCombinedResult> = (
26+
result: Array<QueryObserverResult>,
27+
) => TCombinedResult
28+
2529
export interface QueriesObserverOptions<
2630
TCombinedResult = Array<QueryObserverResult>,
2731
> {
28-
combine?: (result: Array<QueryObserverResult>) => TCombinedResult
32+
combine?: CombineFn<TCombinedResult>
2933
}
3034

3135
export class QueriesObserver<
@@ -55,7 +59,7 @@ export class QueriesObserver<
5559

5660
#setResult(value: Array<QueryObserverResult>) {
5761
this.#result = value
58-
this.#combinedResult = this.#combineResult(value)
62+
this.#combinedResult = this.#combineResult(value, this.#options?.combine)
5963
}
6064

6165
protected onSubscribe(): void {
@@ -151,6 +155,7 @@ export class QueriesObserver<
151155

152156
getOptimisticResult(
153157
queries: Array<QueryObserverOptions>,
158+
combine: CombineFn<TCombinedResult> | undefined,
154159
): [
155160
rawResult: Array<QueryObserverResult>,
156161
combineResult: (r?: Array<QueryObserverResult>) => TCombinedResult,
@@ -164,7 +169,7 @@ export class QueriesObserver<
164169
return [
165170
result,
166171
(r?: Array<QueryObserverResult>) => {
167-
return this.#combineResult(r ?? result)
172+
return this.#combineResult(r ?? result, combine)
168173
},
169174
() => {
170175
return matches.map((match, index) => {
@@ -177,8 +182,10 @@ export class QueriesObserver<
177182
]
178183
}
179184

180-
#combineResult(input: Array<QueryObserverResult>): TCombinedResult {
181-
const combine = this.#options?.combine
185+
#combineResult(
186+
input: Array<QueryObserverResult>,
187+
combine: CombineFn<TCombinedResult> | undefined,
188+
): TCombinedResult {
182189
if (combine) {
183190
return replaceEqualDeep(this.#combinedResult, combine(input))
184191
}

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1167,4 +1167,46 @@ describe('useQueries', () => {
11671167
// no further re-render because data didn't change
11681168
expect(results.length).toBe(length)
11691169
})
1170+
1171+
it('should not have stale closures with combine (#6648)', async () => {
1172+
const key = queryKey()
1173+
1174+
function Page() {
1175+
const [count, setCount] = React.useState(0)
1176+
const queries = useQueries(
1177+
{
1178+
queries: [
1179+
{
1180+
queryKey: key,
1181+
queryFn: () => Promise.resolve('result'),
1182+
},
1183+
],
1184+
combine: (results) => {
1185+
return {
1186+
count,
1187+
res: results.map((res) => res.data).join(','),
1188+
}
1189+
},
1190+
},
1191+
queryClient,
1192+
)
1193+
1194+
return (
1195+
<div>
1196+
<div>
1197+
data: {String(queries.count)} {queries.res}
1198+
</div>
1199+
<button onClick={() => setCount((c) => c + 1)}>inc</button>
1200+
</div>
1201+
)
1202+
}
1203+
1204+
const rendered = render(<Page />)
1205+
1206+
await waitFor(() => rendered.getByText('data: 0 result'))
1207+
1208+
fireEvent.click(rendered.getByRole('button', { name: /inc/i }))
1209+
1210+
await waitFor(() => rendered.getByText('data: 1 result'))
1211+
})
11701212
})

packages/react-query/src/useQueries.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,10 @@ export function useQueries<
282282
)
283283

284284
const [optimisticResult, getCombinedResult, trackResult] =
285-
observer.getOptimisticResult(defaultedQueries)
285+
observer.getOptimisticResult(
286+
defaultedQueries,
287+
(options as QueriesObserverOptions<TCombinedResult>).combine,
288+
)
286289

287290
React.useSyncExternalStore(
288291
React.useCallback(

packages/solid-query/src/createQueries.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,13 +239,23 @@ export function createQueries<
239239
)
240240

241241
const [state, setState] = createStore<TCombinedResult>(
242-
observer.getOptimisticResult(defaultedQueries())[1](),
242+
observer.getOptimisticResult(
243+
defaultedQueries(),
244+
(queriesOptions() as QueriesObserverOptions<TCombinedResult>).combine,
245+
)[1](),
243246
)
244247

245248
createRenderEffect(
246249
on(
247250
() => queriesOptions().queries.length,
248-
() => setState(observer.getOptimisticResult(defaultedQueries())[1]()),
251+
() =>
252+
setState(
253+
observer.getOptimisticResult(
254+
defaultedQueries(),
255+
(queriesOptions() as QueriesObserverOptions<TCombinedResult>)
256+
.combine,
257+
)[1](),
258+
),
249259
),
250260
)
251261

packages/svelte-query/src/createQueries.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,10 @@ export function createQueries<
256256
// @ts-ignore svelte-check thinks this is unused
257257
([$result, $defaultedQueriesStore]) => {
258258
const [rawResult, combineResult, trackResult] =
259-
observer.getOptimisticResult($defaultedQueriesStore)
259+
observer.getOptimisticResult(
260+
$defaultedQueriesStore,
261+
(options as QueriesObserverOptions<TCombinedResult>).combine,
262+
)
260263
$result = rawResult
261264
return combineResult(trackResult())
262265
},

packages/vue-query/src/useQueries.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ export function useQueries<
292292
)
293293
const [, getCombinedResult] = observer.getOptimisticResult(
294294
defaultedQueries.value,
295+
(options as QueriesObserverOptions<TCombinedResult>).combine,
295296
)
296297
const state = ref(getCombinedResult()) as Ref<TCombinedResult>
297298

@@ -307,12 +308,14 @@ export function useQueries<
307308
unsubscribe = observer.subscribe(() => {
308309
const [, getCombinedResultRestoring] = observer.getOptimisticResult(
309310
defaultedQueries.value,
311+
(options as QueriesObserverOptions<TCombinedResult>).combine,
310312
)
311313
state.value = getCombinedResultRestoring()
312314
})
313315
// Subscription would not fire for persisted results
314316
const [, getCombinedResultPersisted] = observer.getOptimisticResult(
315317
defaultedQueries.value,
318+
(options as QueriesObserverOptions<TCombinedResult>).combine,
316319
)
317320
state.value = getCombinedResultPersisted()
318321
}
@@ -329,6 +332,7 @@ export function useQueries<
329332
)
330333
const [, getCombinedResultPersisted] = observer.getOptimisticResult(
331334
defaultedQueries.value,
335+
(options as QueriesObserverOptions<TCombinedResult>).combine,
332336
)
333337
state.value = getCombinedResultPersisted()
334338
},

0 commit comments

Comments
 (0)