Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
5 changes: 5 additions & 0 deletions .changeset/puny-poems-tell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/vue-query': minor
---

Add usePrefetchQuery and usePrefetchInfiniteQuery to vue-query.
12 changes: 8 additions & 4 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,14 @@
"label": "infiniteQueryOptions",
"to": "framework/vue/reference/infiniteQueryOptions"
},
{
"label": "usePrefetchQuery",
"to": "framework/vue/reference/usePrefetchQuery"
},
{
"label": "usePrefetchInfiniteQuery",
"to": "framework/vue/reference/usePrefetchInfiniteQuery"
},
{
"label": "hydration",
"to": "framework/vue/reference/hydration"
Expand Down Expand Up @@ -1293,10 +1301,6 @@
{
"label": "Mutation Property Order",
"to": "eslint/mutation-property-order"
},
{
"label": "Prefer Query Options",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to be unrelated chanage. Please revert

"to": "eslint/prefer-query-options"
}
]
},
Expand Down
6 changes: 6 additions & 0 deletions docs/framework/vue/reference/usePrefetchInfiniteQuery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
id: usePrefetchInfiniteQuery
title: usePrefetchInfiniteQuery
ref: docs/framework/react/reference/usePrefetchInfiniteQuery.md
replace: { '@tanstack/react-query': '@tanstack/vue-query' }
---
6 changes: 6 additions & 0 deletions docs/framework/vue/reference/usePrefetchQuery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
id: usePrefetchQuery
title: usePrefetchQuery
ref: docs/framework/react/reference/usePrefetchQuery.md
replace: { '@tanstack/react-query': '@tanstack/vue-query' }
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { assertType, describe, expectTypeOf, it } from 'vitest'
import { ref } from 'vue-demi'
import { skipToken } from '@tanstack/query-core'
import { usePrefetchInfiniteQuery } from '..'

describe('usePrefetchInfiniteQuery', () => {
it('should return nothing', () => {
const result = usePrefetchInfiniteQuery({
queryKey: ['key'],
queryFn: () => Promise.resolve(5),
initialPageParam: 1,
getNextPageParam: () => 1,
})

expectTypeOf(result).toEqualTypeOf<void>()
})

it('should require initialPageParam and getNextPageParam', () => {
assertType(
// @ts-expect-error TS2345
usePrefetchInfiniteQuery({
queryKey: ['key'],
queryFn: () => Promise.resolve(5),
}),
)
})

it('should not allow refetchInterval, enabled or throwOnError options', () => {
assertType(
usePrefetchInfiniteQuery({
queryKey: ['key'],
queryFn: () => Promise.resolve(5),
initialPageParam: 1,
getNextPageParam: () => 1,
// @ts-expect-error TS2353
refetchInterval: 1000,
}),
)

assertType(
usePrefetchInfiniteQuery({
queryKey: ['key'],
queryFn: () => Promise.resolve(5),
initialPageParam: 1,
getNextPageParam: () => 1,
// @ts-expect-error TS2353
enabled: true,
}),
)

assertType(
usePrefetchInfiniteQuery({
queryKey: ['key'],
queryFn: () => Promise.resolve(5),
initialPageParam: 1,
getNextPageParam: () => 1,
// @ts-expect-error TS2353
throwOnError: true,
}),
)
})

it('should accept refs in infinite query options', () => {
assertType(
usePrefetchInfiniteQuery({
queryKey: ['key', ref('id')],
queryFn: () => Promise.resolve(5),
initialPageParam: ref(1),
getNextPageParam: () => 1,
staleTime: ref(1000),
}),
)
})

it('should not allow skipToken in queryFn', () => {
assertType(
usePrefetchInfiniteQuery({
queryKey: ['key'],
initialPageParam: 1,
getNextPageParam: () => 1,
// @ts-expect-error
queryFn: skipToken,
}),
)

assertType(
usePrefetchInfiniteQuery({
queryKey: ['key'],
initialPageParam: 1,
getNextPageParam: () => 1,
// @ts-expect-error
queryFn: Math.random() > 0.5 ? skipToken : () => Promise.resolve(5),
}),
)
})
})
157 changes: 157 additions & 0 deletions packages/vue-query/src/__tests__/usePrefetchInfiniteQuery.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
import { nextTick, ref } from 'vue-demi'
import { QueryClient } from '../queryClient'
import { usePrefetchInfiniteQuery } from '../usePrefetchInfiniteQuery'

describe('usePrefetchInfiniteQuery', () => {
beforeEach(() => {
vi.useFakeTimers()
})

afterEach(() => {
vi.useRealTimers()
})

test('should prefetch infinite query if query state does not exist', () => {
const queryClient = new QueryClient()
const prefetchInfiniteQuerySpy = vi.spyOn(
queryClient,
'prefetchInfiniteQuery',
)
const queryFn = vi.fn(() =>
Promise.resolve({ data: 'prefetched', currentPage: 1 }),
)

usePrefetchInfiniteQuery(
{
queryKey: ['prefetch-infinite-query'],
queryFn,
initialPageParam: 1,
getNextPageParam: () => undefined,
},
queryClient,
)

expect(prefetchInfiniteQuerySpy).toHaveBeenCalledTimes(1)
expect(prefetchInfiniteQuerySpy).toHaveBeenCalledWith({
queryKey: ['prefetch-infinite-query'],
queryFn,
initialPageParam: 1,
getNextPageParam: expect.any(Function),
})
})

test('should not prefetch infinite query if query state exists', () => {
const queryClient = new QueryClient()
const prefetchInfiniteQuerySpy = vi.spyOn(
queryClient,
'prefetchInfiniteQuery',
)
const queryFn = vi.fn(() =>
Promise.resolve({ data: 'prefetched', currentPage: 1 }),
)

queryClient.setQueryData(['prefetch-infinite-query-existing'], {
pages: [{ data: 'existing', currentPage: 1 }],
pageParams: [1],
})

usePrefetchInfiniteQuery(
{
queryKey: ['prefetch-infinite-query-existing'],
queryFn,
initialPageParam: 1,
getNextPageParam: () => undefined,
},
queryClient,
)

expect(prefetchInfiniteQuerySpy).not.toHaveBeenCalled()
})

test('should unwrap refs in infinite query options', () => {
const queryClient = new QueryClient()
const prefetchInfiniteQuerySpy = vi.spyOn(
queryClient,
'prefetchInfiniteQuery',
)
const nestedRef = ref('value')

usePrefetchInfiniteQuery(
{
queryKey: ['prefetch-infinite-query-ref', nestedRef],
queryFn: () => Promise.resolve({ data: 'prefetched', currentPage: 1 }),
initialPageParam: 1,
getNextPageParam: () => undefined,
},
queryClient,
)

expect(prefetchInfiniteQuerySpy).toHaveBeenCalledWith(
expect.objectContaining({
queryKey: ['prefetch-infinite-query-ref', 'value'],
}),
)
})

test('should prefetch infinite query again when query key changes reactively', async () => {
const queryClient = new QueryClient()
const prefetchInfiniteQuerySpy = vi.spyOn(
queryClient,
'prefetchInfiniteQuery',
)
const keyRef = ref('first')

usePrefetchInfiniteQuery(
() => ({
queryKey: ['prefetch-infinite-query-reactive', keyRef.value],
queryFn: () => Promise.resolve({ data: keyRef.value, currentPage: 1 }),
initialPageParam: 1,
getNextPageParam: () => undefined,
}),
queryClient,
)

expect(prefetchInfiniteQuerySpy).toHaveBeenCalledTimes(1)
expect(prefetchInfiniteQuerySpy).toHaveBeenLastCalledWith(
expect.objectContaining({
queryKey: ['prefetch-infinite-query-reactive', 'first'],
}),
)

keyRef.value = 'second'
await nextTick()

expect(prefetchInfiniteQuerySpy).toHaveBeenCalledTimes(2)
expect(prefetchInfiniteQuerySpy).toHaveBeenLastCalledWith(
expect.objectContaining({
queryKey: ['prefetch-infinite-query-reactive', 'second'],
}),
)
})

test('should warn when used outside of setup function in development mode', () => {
vi.stubEnv('NODE_ENV', 'development')
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})

try {
usePrefetchInfiniteQuery(
{
queryKey: ['outside-scope-prefetch-infinite-query'],
queryFn: () =>
Promise.resolve({ data: 'prefetched', currentPage: 1 }),
initialPageParam: 1,
getNextPageParam: () => undefined,
},
new QueryClient(),
)

expect(warnSpy).toHaveBeenCalledWith(
'vue-query composable like "useQuery()" should only be used inside a "setup()" function or a running effect scope. They might otherwise lead to memory leaks.',
)
} finally {
warnSpy.mockRestore()
vi.unstubAllEnvs()
}
})
})
72 changes: 72 additions & 0 deletions packages/vue-query/src/__tests__/usePrefetchQuery.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { assertType, describe, expectTypeOf, it } from 'vitest'
import { ref } from 'vue-demi'
import { skipToken } from '@tanstack/query-core'
import { usePrefetchQuery } from '..'

describe('usePrefetchQuery', () => {
it('should return nothing', () => {
const result = usePrefetchQuery({
queryKey: ['key'],
queryFn: () => Promise.resolve(5),
})

expectTypeOf(result).toEqualTypeOf<void>()
})

it('should not allow refetchInterval, enabled or throwOnError options', () => {
assertType(
usePrefetchQuery({
queryKey: ['key'],
queryFn: () => Promise.resolve(5),
// @ts-expect-error TS2353
refetchInterval: 1000,
}),
)

assertType(
usePrefetchQuery({
queryKey: ['key'],
queryFn: () => Promise.resolve(5),
// @ts-expect-error TS2353
enabled: true,
}),
)

assertType(
usePrefetchQuery({
queryKey: ['key'],
queryFn: () => Promise.resolve(5),
// @ts-expect-error TS2353
throwOnError: true,
}),
)
})

it('should accept refs in query options', () => {
assertType(
usePrefetchQuery({
queryKey: ['key', ref('id')],
queryFn: () => Promise.resolve(5),
staleTime: ref(1000),
}),
)
})

it('should not allow skipToken in queryFn', () => {
assertType(
usePrefetchQuery({
queryKey: ['key'],
// @ts-expect-error
queryFn: skipToken,
}),
)

assertType(
usePrefetchQuery({
queryKey: ['key'],
// @ts-expect-error
queryFn: Math.random() > 0.5 ? skipToken : () => Promise.resolve(5),
}),
)
})
})
Loading