Skip to content

Commit b66f92a

Browse files
fix(vue-query): useQueries length change detection fix for vue2 (#4934)
* fix(vue-query): useQueries length change detection fix for vue2 BREAKING CHANGE: useQueries returns `ref` instead of `reactive`
1 parent 50b6a81 commit b66f92a

File tree

9 files changed

+442
-209
lines changed

9 files changed

+442
-209
lines changed

docs/config.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,10 @@
565565
{
566566
"label": "Does this replace [Vuex, Pinia]?",
567567
"to": "vue/guides/does-this-replace-client-state"
568+
},
569+
{
570+
"label": "Migrating to v5",
571+
"to": "react/guides/migrating-to-v5"
568572
}
569573
]
570574
},

docs/vue/guides/migrating-to-v5.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
id: migrating-to-tanstack-query-5
3+
title: Migrating to TanStack Query v5
4+
ref: docs/react/guides/migrating-to-v5.md
5+
---
6+
7+
[//]: # 'FrameworkBreakingChanges'
8+
9+
## Vue Query Breaking Changes
10+
11+
### `useQueries` composable returns `ref` instead of `reactive`
12+
13+
To fix compatibility with Vue 2, `useQueries` hook returns now `queries` array wrapped in `ref`.
14+
Previously `reactive` was returned which led to multiple problems:
15+
16+
- User could spread return value loosing reactivity.
17+
- `readonly` wrapper used for return value was breaking Vue 2 reactivity detection mechanism. This was a silent issue in Vue 2.6, but appeared as error in Vue 2.7.
18+
- Vue 2 does not support arrays as a root value of `reactive`.
19+
20+
With this change all of those issues are fixed.
21+
22+
Also this aligns `useQueries` with other composables which return all of the values as `refs`.
23+
24+
[//]: # 'FrameworkBreakingChanges'
Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
1-
const vue = jest.requireActual("vue-demi");
1+
// Hide annoying console warnings for Vue2
2+
import Vue from 'vue2'
3+
Vue.config.productionTip = false
4+
Vue.config.devtools = false
5+
6+
// Hide annoying console warnings for Vue2
7+
import Vue27 from 'vue2.7'
8+
Vue27.config.productionTip = false
9+
Vue27.config.devtools = false
10+
11+
const vue = jest.requireActual('vue-demi')
212

313
module.exports = {
414
...vue,
515
inject: jest.fn(),
616
provide: jest.fn(),
717
onScopeDispose: jest.fn(),
818
getCurrentInstance: jest.fn(() => ({ proxy: {} })),
9-
};
19+
}

packages/vue-query/package.json

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@
3030
"clean": "rimraf ./build",
3131
"test:eslint": "eslint --ext .ts ./src",
3232
"test:types": "tsc",
33-
"test:lib": "jest --config ./jest.config.ts",
33+
"test:lib": "pnpm run test:2 && pnpm run test:2.7 && pnpm run test:3",
34+
"test:2": "vue-demi-switch 2 vue2 && jest --config ./jest.config.ts",
35+
"test:2.7": "vue-demi-switch 2.7 vue2.7 && jest --config ./jest.config.ts",
36+
"test:3": "vue-demi-switch 3 && jest --config ./jest.config.ts",
3437
"test:lib:dev": "pnpm run test:lib --watch",
3538
"build:types": "tsc --build"
3639
},
@@ -40,13 +43,14 @@
4043
],
4144
"devDependencies": {
4245
"@vue/composition-api": "1.7.1",
43-
"vue": "^3.2.40",
44-
"vue2": "npm:vue@2"
46+
"vue": "^3.2.47",
47+
"vue2": "npm:[email protected]",
48+
"vue2.7": "npm:[email protected]"
4549
},
4650
"dependencies": {
4751
"@tanstack/query-core": "workspace:*",
48-
"@vue/devtools-api": "^6.4.2",
49-
"@tanstack/match-sorter-utils": "^8.1.1",
52+
"@vue/devtools-api": "^6.5.0",
53+
"@tanstack/match-sorter-utils": "^8.7.6",
5054
"vue-demi": "^0.13.11"
5155
},
5256
"peerDependencies": {

packages/vue-query/src/__tests__/queryCache.test.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,12 @@ describe('QueryCache', () => {
3737
})
3838
})
3939

40-
test('should properly unwrap one parameter', async () => {
40+
test('should default to empty filters', async () => {
4141
const queryCache = new QueryCache()
4242

43-
queryCache.findAll({
44-
queryKey: ref(['baz']),
45-
})
43+
queryCache.findAll()
4644

47-
expect(QueryCacheOrigin.prototype.findAll).toBeCalledWith({
48-
queryKey: ['baz'],
49-
})
45+
expect(QueryCacheOrigin.prototype.findAll).toBeCalledWith({})
5046
})
5147
})
5248
})

packages/vue-query/src/__tests__/useQueries.test.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { onScopeDispose, reactive } from 'vue-demi'
1+
import { onScopeDispose, ref } from 'vue-demi'
22

33
import {
44
flushPromises,
@@ -26,7 +26,7 @@ describe('useQueries', () => {
2626
]
2727
const queriesState = useQueries({ queries })
2828

29-
expect(queriesState).toMatchObject([
29+
expect(queriesState.value).toMatchObject([
3030
{
3131
status: 'loading',
3232
isLoading: true,
@@ -57,7 +57,7 @@ describe('useQueries', () => {
5757

5858
await flushPromises()
5959

60-
expect(queriesState).toMatchObject([
60+
expect(queriesState.value).toMatchObject([
6161
{
6262
status: 'success',
6363
isLoading: false,
@@ -88,7 +88,7 @@ describe('useQueries', () => {
8888

8989
await flushPromises()
9090

91-
expect(queriesState).toMatchObject([
91+
expect(queriesState.value).toMatchObject([
9292
{
9393
status: 'error',
9494
isLoading: false,
@@ -105,7 +105,7 @@ describe('useQueries', () => {
105105
})
106106

107107
test('should return state for new queries', async () => {
108-
const queries = reactive([
108+
const queries = ref([
109109
{
110110
queryKey: ['key31'],
111111
queryFn: getSimpleFetcherWithReturnData('value31'),
@@ -123,9 +123,9 @@ describe('useQueries', () => {
123123

124124
await flushPromises()
125125

126-
queries.splice(
126+
queries.value.splice(
127127
0,
128-
queries.length,
128+
queries.value.length,
129129
{
130130
queryKey: ['key31'],
131131
queryFn: getSimpleFetcherWithReturnData('value31'),
@@ -139,8 +139,8 @@ describe('useQueries', () => {
139139
await flushPromises()
140140
await flushPromises()
141141

142-
expect(queriesState.length).toEqual(2)
143-
expect(queriesState).toMatchObject([
142+
expect(queriesState.value.length).toEqual(2)
143+
expect(queriesState.value).toMatchObject([
144144
{
145145
data: 'value31',
146146
status: 'success',
@@ -177,7 +177,7 @@ describe('useQueries', () => {
177177
const queriesState = useQueries({ queries })
178178
await flushPromises()
179179

180-
expect(queriesState).toMatchObject([
180+
expect(queriesState.value).toMatchObject([
181181
{
182182
status: 'loading',
183183
isLoading: true,

packages/vue-query/src/__tests__/vueQueryPlugin.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -197,10 +197,10 @@ describe('VueQueryPlugin', () => {
197197
})
198198

199199
appMock._mixin.beforeCreate?.call(appMock)
200+
const client = appMock._provided.VUE_QUERY_CLIENT as QueryClient
201+
const defaultOptions = client.getDefaultOptions()
200202

201-
expect(appMock._provided).toMatchObject({
202-
VUE_QUERY_CLIENT: expect.objectContaining(config),
203-
})
203+
expect(defaultOptions).toEqual(config.defaultOptions)
204204
},
205205
)
206206

@@ -333,14 +333,14 @@ describe('VueQueryPlugin', () => {
333333
})
334334

335335
expect(customClient.isRestoring.value).toBeTruthy()
336-
expect(queries[0].isFetching).toBeFalsy()
337-
expect(queries[0].data).toStrictEqual(undefined)
336+
expect(queries.value[0].isFetching).toBeFalsy()
337+
expect(queries.value[0].data).toStrictEqual(undefined)
338338
expect(fnSpy).toHaveBeenCalledTimes(0)
339339

340340
await flushPromises()
341341

342342
expect(customClient.isRestoring.value).toBeFalsy()
343-
expect(queries[0].data).toStrictEqual({ foo: 'bar' })
343+
expect(queries.value[0].data).toStrictEqual({ foo: 'bar' })
344344
expect(fnSpy).toHaveBeenCalledTimes(0)
345345
})
346346
})

packages/vue-query/src/useQueries.ts

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,8 @@ import type {
44
QueriesPlaceholderDataFunction,
55
QueryKey,
66
} from '@tanstack/query-core'
7-
import {
8-
computed,
9-
onScopeDispose,
10-
reactive,
11-
readonly,
12-
ref,
13-
watch,
14-
} from 'vue-demi'
7+
import type { Ref } from 'vue-demi'
8+
import { computed, onScopeDispose, readonly, ref, watch } from 'vue-demi'
159

1610
import type { QueryFunction, QueryObserverResult } from '@tanstack/query-core'
1711

@@ -157,7 +151,7 @@ export function useQueries<T extends any[]>({
157151
}: {
158152
queries: MaybeRefDeep<UseQueriesOptionsArg<T>>
159153
queryClient?: QueryClient
160-
}): Readonly<UseQueriesResults<T>> {
154+
}): Readonly<Ref<UseQueriesResults<T>>> {
161155
const client = queryClient || useQueryClient()
162156

163157
const defaultedQueries = computed(() =>
@@ -172,7 +166,7 @@ export function useQueries<T extends any[]>({
172166
)
173167

174168
const observer = new QueriesObserver(client, defaultedQueries.value)
175-
const state = reactive(observer.getCurrentResult())
169+
const state = ref(observer.getCurrentResult())
176170

177171
const unsubscribe = ref(() => {
178172
// noop
@@ -184,12 +178,12 @@ export function useQueries<T extends any[]>({
184178
if (!isRestoring) {
185179
unsubscribe.value()
186180
unsubscribe.value = observer.subscribe((result) => {
187-
state.splice(0, result.length, ...result)
181+
state.value.splice(0, result.length, ...result)
188182
})
189183
// Subscription would not fire for persisted results
190-
state.splice(
184+
state.value.splice(
191185
0,
192-
state.length,
186+
state.value.length,
193187
...observer.getOptimisticResult(defaultedQueries.value),
194188
)
195189
}
@@ -201,7 +195,7 @@ export function useQueries<T extends any[]>({
201195
defaultedQueries,
202196
() => {
203197
observer.setQueries(defaultedQueries.value)
204-
state.splice(0, state.length, ...observer.getCurrentResult())
198+
state.value.splice(0, state.value.length, ...observer.getCurrentResult())
205199
},
206200
{ deep: true },
207201
)
@@ -210,5 +204,5 @@ export function useQueries<T extends any[]>({
210204
unsubscribe.value()
211205
})
212206

213-
return readonly(state) as UseQueriesResults<T>
207+
return readonly(state) as Readonly<Ref<UseQueriesResults<T>>>
214208
}

0 commit comments

Comments
 (0)