Skip to content

Commit cae2524

Browse files
authored
fix(core): do not inform QueriesObserver subscribers if combined result hasn't changed (#8153)
1 parent 05ccd65 commit cae2524

File tree

2 files changed

+137
-7
lines changed

2 files changed

+137
-7
lines changed

packages/query-core/src/queriesObserver.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export class QueriesObserver<
3838
#client: QueryClient
3939
#result!: Array<QueryObserverResult>
4040
#queries: Array<QueryObserverOptions>
41+
#options?: QueriesObserverOptions<TCombinedResult>
4142
#observers: Array<QueryObserver>
4243
#combinedResult?: TCombinedResult
4344
#lastCombine?: CombineFn<TCombinedResult>
@@ -46,11 +47,12 @@ export class QueriesObserver<
4647
constructor(
4748
client: QueryClient,
4849
queries: Array<QueryObserverOptions<any, any, any, any, any>>,
49-
_options?: QueriesObserverOptions<TCombinedResult>,
50+
options?: QueriesObserverOptions<TCombinedResult>,
5051
) {
5152
super()
5253

5354
this.#client = client
55+
this.#options = options
5456
this.#queries = []
5557
this.#observers = []
5658
this.#result = []
@@ -83,10 +85,11 @@ export class QueriesObserver<
8385

8486
setQueries(
8587
queries: Array<QueryObserverOptions>,
86-
_options?: QueriesObserverOptions<TCombinedResult>,
88+
options?: QueriesObserverOptions<TCombinedResult>,
8789
notifyOptions?: NotifyOptions,
8890
): void {
8991
this.#queries = queries
92+
this.#options = options
9093

9194
notifyManager.batch(() => {
9295
const prevObservers = this.#observers
@@ -268,11 +271,21 @@ export class QueriesObserver<
268271
}
269272

270273
#notify(): void {
271-
notifyManager.batch(() => {
272-
this.listeners.forEach((listener) => {
273-
listener(this.#result)
274-
})
275-
})
274+
if (this.hasListeners()) {
275+
const previousResult = this.#combinedResult
276+
const newResult = this.#combineResult(
277+
this.#result,
278+
this.#options?.combine,
279+
)
280+
281+
if (previousResult !== newResult) {
282+
notifyManager.batch(() => {
283+
this.listeners.forEach((listener) => {
284+
listener(this.#result)
285+
})
286+
})
287+
}
288+
}
276289
}
277290
}
278291

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

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1431,4 +1431,121 @@ describe('useQueries', () => {
14311431
// state changed, re-run combine
14321432
expect(spy).toHaveBeenCalledTimes(4)
14331433
})
1434+
1435+
it('should not re-render if combine returns a stable reference', async () => {
1436+
const key1 = queryKey()
1437+
const key2 = queryKey()
1438+
1439+
const client = new QueryClient()
1440+
1441+
const queryFns: Array<string> = []
1442+
let renders = 0
1443+
1444+
function Page() {
1445+
const data = useQueries(
1446+
{
1447+
queries: [
1448+
{
1449+
queryKey: [key1],
1450+
queryFn: async () => {
1451+
await sleep(10)
1452+
queryFns.push('first result')
1453+
return 'first result'
1454+
},
1455+
},
1456+
{
1457+
queryKey: [key2],
1458+
queryFn: async () => {
1459+
await sleep(20)
1460+
queryFns.push('second result')
1461+
return 'second result'
1462+
},
1463+
},
1464+
],
1465+
combine: () => 'foo',
1466+
},
1467+
client,
1468+
)
1469+
1470+
renders++
1471+
1472+
return (
1473+
<div>
1474+
<div>data: {data}</div>
1475+
</div>
1476+
)
1477+
}
1478+
1479+
const rendered = render(<Page />)
1480+
1481+
await waitFor(() => rendered.getByText('data: foo'))
1482+
1483+
await waitFor(() =>
1484+
expect(queryFns).toEqual(['first result', 'second result']),
1485+
)
1486+
1487+
expect(renders).toBe(1)
1488+
})
1489+
1490+
it('should re-render once combine returns a different reference', async () => {
1491+
const key1 = queryKey()
1492+
const key2 = queryKey()
1493+
const key3 = queryKey()
1494+
1495+
const client = new QueryClient()
1496+
1497+
let renders = 0
1498+
1499+
function Page() {
1500+
const data = useQueries(
1501+
{
1502+
queries: [
1503+
{
1504+
queryKey: [key1],
1505+
queryFn: async () => {
1506+
await sleep(10)
1507+
return 'first result'
1508+
},
1509+
},
1510+
{
1511+
queryKey: [key2],
1512+
queryFn: async () => {
1513+
await sleep(15)
1514+
return 'second result'
1515+
},
1516+
},
1517+
{
1518+
queryKey: [key3],
1519+
queryFn: async () => {
1520+
await sleep(20)
1521+
return 'third result'
1522+
},
1523+
},
1524+
],
1525+
combine: (results) => {
1526+
const isPending = results.some((res) => res.isPending)
1527+
1528+
return isPending ? 'pending' : 'foo'
1529+
},
1530+
},
1531+
client,
1532+
)
1533+
1534+
renders++
1535+
1536+
return (
1537+
<div>
1538+
<div>data: {data}</div>
1539+
</div>
1540+
)
1541+
}
1542+
1543+
const rendered = render(<Page />)
1544+
1545+
await waitFor(() => rendered.getByText('data: pending'))
1546+
await waitFor(() => rendered.getByText('data: foo'))
1547+
1548+
// one with pending, one with foo
1549+
expect(renders).toBe(2)
1550+
})
14341551
})

0 commit comments

Comments
 (0)