Skip to content

Commit

Permalink
feat(vue-query): support injectable contexts (#5886)
Browse files Browse the repository at this point in the history
* feat(vue-query): support injectable contexts

This feature requires Vue 3.3.0, which has been out for a while now. It
allows using vue-query APIs in places where it is valid to use them but
currently throws an error.

- `hasInjectionContext()`: vuejs/core#8111
- `app.runWithContext()`: vuejs/core#7451

* refactor: add dev warning for effectScope

* fix: add memory leak warnings, fix tests

* chore: bump vue-demi

* chore: bump vue version in integrations

---------

Co-authored-by: Damian Osipiuk <osipiukd+git@gmail.com>
  • Loading branch information
posva and DamianOsipiuk committed Aug 30, 2023
1 parent a496b3d commit a0fae50
Show file tree
Hide file tree
Showing 16 changed files with 581 additions and 243 deletions.
2 changes: 1 addition & 1 deletion examples/vue/basic/package.json
Expand Up @@ -9,7 +9,7 @@
},
"dependencies": {
"@tanstack/vue-query": "^5.0.0-beta.0",
"vue": "^3.2.47"
"vue": "^3.3.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.3",
Expand Down
2 changes: 1 addition & 1 deletion examples/vue/dependent-queries/package.json
Expand Up @@ -9,7 +9,7 @@
},
"dependencies": {
"@tanstack/vue-query": "^5.0.0-beta.0",
"vue": "^3.2.47"
"vue": "^3.3.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.3",
Expand Down
2 changes: 1 addition & 1 deletion examples/vue/persister/package.json
Expand Up @@ -11,7 +11,7 @@
"@tanstack/query-persist-client-core": "^5.0.0-beta.0",
"@tanstack/query-sync-storage-persister": "^5.0.0-beta.0",
"@tanstack/vue-query": "^5.0.0-beta.0",
"vue": "^3.2.47"
"vue": "^3.3.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.3",
Expand Down
2 changes: 1 addition & 1 deletion integrations/vue-vite/package.json
Expand Up @@ -8,7 +8,7 @@
"dependencies": {
"@tanstack/vue-query": "workspace:*",
"@vitejs/plugin-vue": "^4.2.3",
"vue": "^3.2.47",
"vue": "^3.3.0",
"vite": "^4.4.4"
}
}
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -83,11 +83,12 @@
"solid-js": "^1.7.8",
"stream-to-array": "^2.3.0",
"tsup": "^7.1.0",
"ts-node": "^10.7.0",
"type-fest": "^3.13.0",
"typescript": "^5.0.4",
"vite": "^4.4.4",
"vitest": "^0.33.0",
"vue": "^3.2.47"
"vue": "^3.3.0"
},
"pnpm": {
"patchedDependencies": {
Expand Down
8 changes: 4 additions & 4 deletions packages/vue-query/package.json
Expand Up @@ -61,17 +61,17 @@
"@tanstack/match-sorter-utils": "^8.8.4",
"@tanstack/query-core": "workspace:*",
"@vue/devtools-api": "^6.5.0",
"vue-demi": "^0.13.11"
"vue-demi": "^0.14.6"
},
"devDependencies": {
"@vue/composition-api": "1.7.1",
"vue": "^3.2.47",
"@vue/composition-api": "1.7.2",
"vue": "^3.3.0",
"vue2": "npm:vue@2.6",
"vue2.7": "npm:vue@2.7"
},
"peerDependencies": {
"@vue/composition-api": "^1.1.2",
"vue": "^2.5.0 || ^3.0.0"
"vue": "^2.6.0 || ^3.3.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
Expand Down
8 changes: 4 additions & 4 deletions packages/vue-query/src/__tests__/useQueryClient.test.ts
@@ -1,12 +1,12 @@
import { getCurrentInstance, inject } from 'vue-demi'
import { hasInjectionContext, inject } from 'vue-demi'
import { vi } from 'vitest'
import { useQueryClient } from '../useQueryClient'
import { VUE_QUERY_CLIENT } from '../utils'
import type { Mock } from 'vitest'

describe('useQueryClient', () => {
const injectSpy = inject as Mock
const getCurrentInstanceSpy = getCurrentInstance as Mock
const hasInjectionContextSpy = hasInjectionContext as Mock

beforeEach(() => {
vi.restoreAllMocks()
Expand All @@ -32,10 +32,10 @@ describe('useQueryClient', () => {
})

test('should throw an error when used outside of setup function', () => {
getCurrentInstanceSpy.mockReturnValueOnce(undefined)
hasInjectionContextSpy.mockReturnValueOnce(false)

expect(useQueryClient).toThrowError()
expect(getCurrentInstanceSpy).toHaveBeenCalledTimes(1)
expect(hasInjectionContextSpy).toHaveBeenCalledTimes(1)
})

test('should call inject with a custom key as a suffix', () => {
Expand Down
9 changes: 9 additions & 0 deletions packages/vue-query/src/useBaseQuery.ts
@@ -1,5 +1,6 @@
import {
computed,
getCurrentScope,
onScopeDispose,
reactive,
readonly,
Expand Down Expand Up @@ -64,6 +65,14 @@ export function useBaseQuery<
>,
queryClient?: QueryClient,
): UseBaseQueryReturnType<TData, TError> {
if (process.env.NODE_ENV === 'development') {
if (!getCurrentScope()) {
console.warn(
'vue-query composables like "uesQuery()" should only be used inside a "setup()" function or a running effect scope. They might otherwise lead to memory leaks.',
)
}
}

const client = queryClient || useQueryClient()

const defaultedOptions = computed(() => {
Expand Down
10 changes: 9 additions & 1 deletion packages/vue-query/src/useIsFetching.ts
@@ -1,4 +1,4 @@
import { onScopeDispose, ref, watchSyncEffect } from 'vue-demi'
import { getCurrentScope, onScopeDispose, ref, watchSyncEffect } from 'vue-demi'
import { useQueryClient } from './useQueryClient'
import type { Ref } from 'vue-demi'
import type { QueryFilters as QF } from '@tanstack/query-core'
Expand All @@ -11,6 +11,14 @@ export function useIsFetching(
fetchingFilters: MaybeRefDeep<QF> = {},
queryClient?: QueryClient,
): Ref<number> {
if (process.env.NODE_ENV === 'development') {
if (!getCurrentScope()) {
console.warn(
'vue-query composables like "uesQuery()" should only be used inside a "setup()" function or a running effect scope. They might otherwise lead to memory leaks.',
)
}
}

const client = queryClient || useQueryClient()

const isFetching = ref()
Expand Down
9 changes: 9 additions & 0 deletions packages/vue-query/src/useMutation.ts
@@ -1,5 +1,6 @@
import {
computed,
getCurrentScope,
onScopeDispose,
reactive,
readonly,
Expand Down Expand Up @@ -64,6 +65,14 @@ export function useMutation<
>,
queryClient?: QueryClient,
): UseMutationReturnType<TData, TError, TVariables, TContext> {
if (process.env.NODE_ENV === 'development') {
if (!getCurrentScope()) {
console.warn(
'vue-query composables like "uesQuery()" should only be used inside a "setup()" function or a running effect scope. They might otherwise lead to memory leaks.',
)
}
}

const client = queryClient || useQueryClient()
const options = computed(() => {
return client.defaultMutationOptions(cloneDeepUnref(mutationOptions))
Expand Down
17 changes: 16 additions & 1 deletion packages/vue-query/src/useMutationState.ts
@@ -1,4 +1,11 @@
import { computed, onScopeDispose, readonly, ref, watch } from 'vue-demi'
import {
computed,
getCurrentScope,
onScopeDispose,
readonly,
ref,
watch,
} from 'vue-demi'
import { useQueryClient } from './useQueryClient'
import { cloneDeepUnref } from './utils'
import type { DeepReadonly, Ref } from 'vue-demi'
Expand All @@ -18,6 +25,14 @@ export function useIsMutating(
filters: MutationFilters = {},
queryClient?: QueryClient,
): Ref<number> {
if (process.env.NODE_ENV === 'development') {
if (!getCurrentScope()) {
console.warn(
'vue-query composables like "uesQuery()" should only be used inside a "setup()" function or a running effect scope. They might otherwise lead to memory leaks.',
)
}
}

const client = queryClient || useQueryClient()
const unreffedFilters = computed(() => ({
...cloneDeepUnref(filters),
Expand Down
17 changes: 16 additions & 1 deletion packages/vue-query/src/useQueries.ts
@@ -1,6 +1,13 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { QueriesObserver } from '@tanstack/query-core'
import { computed, onScopeDispose, readonly, ref, watch } from 'vue-demi'
import {
computed,
getCurrentScope,
onScopeDispose,
readonly,
ref,
watch,
} from 'vue-demi'

import { useQueryClient } from './useQueryClient'
import { cloneDeepUnref } from './utils'
Expand Down Expand Up @@ -167,6 +174,14 @@ export function useQueries<
},
queryClient?: QueryClient,
): Readonly<Ref<TCombinedResult>> {
if (process.env.NODE_ENV === 'development') {
if (!getCurrentScope()) {
console.warn(
'vue-query composables like "uesQuery()" should only be used inside a "setup()" function or a running effect scope. They might otherwise lead to memory leaks.',
)
}
}

const client = queryClient || useQueryClient()

const defaultedQueries = computed(() =>
Expand Down
11 changes: 6 additions & 5 deletions packages/vue-query/src/useQueryClient.ts
@@ -1,13 +1,14 @@
import { getCurrentInstance, inject } from 'vue-demi'
import { hasInjectionContext, inject } from 'vue-demi'

import { getClientKey } from './utils'
import type { QueryClient } from './queryClient'

export function useQueryClient(id = ''): QueryClient {
const vm = getCurrentInstance()?.proxy

if (!vm) {
throw new Error('vue-query hooks can only be used inside setup() function.')
// ensures that `inject()` can be used
if (!hasInjectionContext()) {
throw new Error(
'vue-query hooks can only be used inside setup() function or functions that support injection context.',
)
}

const key = getClientKey(id)
Expand Down
10 changes: 1 addition & 9 deletions packages/vue-query/test-setup.ts
@@ -1,14 +1,5 @@
import { vi } from 'vitest'

import Vue from 'vue2'
Vue.config.productionTip = false
Vue.config.devtools = false

// Hide annoying console warnings for Vue2
import Vue27 from 'vue2.7'
Vue27.config.productionTip = false
Vue27.config.devtools = false

vi.mock('vue-demi', async () => {
const vue = await vi.importActual('vue-demi')
return {
Expand All @@ -17,5 +8,6 @@ vi.mock('vue-demi', async () => {
provide: vi.fn(),
onScopeDispose: vi.fn(),
getCurrentInstance: vi.fn(() => ({ proxy: {} })),
hasInjectionContext: vi.fn(() => true),
}
})
5 changes: 5 additions & 0 deletions packages/vue-query/vitest.config.ts
Expand Up @@ -9,5 +9,10 @@ export default defineConfig({
globals: true,
setupFiles: ['test-setup.ts'],
coverage: { provider: 'istanbul' },
onConsoleLog: function (log) {
if (log.includes('Download the Vue Devtools extension')) {
return false
}
},
},
})

0 comments on commit a0fae50

Please sign in to comment.