T
TanStack11mo ago
sensitive-blue

QueryOptions as generic prop

Im implementing a Link component with a prefetch prop, Whenever someone hovers a link and it has the prefetch prop i want to call queryClient.prefetchQuery(props.prefetch) My problem is to properly implement the prop type via typescript. I have some queries set up via queryOptions like so
export const authQueries = {
getMe: () =>
queryOptions({
queryKey: ['getMe'],
queryFn: async ({ signal }) => {
const { data } = await authService.getMe({
signal
})
return data
},
})
}
export const authQueries = {
getMe: () =>
queryOptions({
queryKey: ['getMe'],
queryFn: async ({ signal }) => {
const { data } = await authService.getMe({
signal
})
return data
},
})
}
Now in my Link component it looks like this
<script lang="ts" setup>
import { useQueryClient, type UseQueriesOptions } from '@tanstack/vue-query'
import type { RouteLocationRaw } from 'vue-router'

const props = defineProps<{
to: RouteLocationRaw
prefetch?: any
}>()


const queryClient = useQueryClient()
const prefetch = () => {
if (!props.prefetch) return
queryClient.prefetchQuery(props.prefetch)
}
</script>
<template>
<RouterLink :to custom v-slot="{ isExactActive, href, navigate }">
<a :href="href" @mouseover="prefetch" @focus="prefetch" @click="navigate"
<slot></slot>
</a>
</RouterLink>
</template>
<script lang="ts" setup>
import { useQueryClient, type UseQueriesOptions } from '@tanstack/vue-query'
import type { RouteLocationRaw } from 'vue-router'

const props = defineProps<{
to: RouteLocationRaw
prefetch?: any
}>()


const queryClient = useQueryClient()
const prefetch = () => {
if (!props.prefetch) return
queryClient.prefetchQuery(props.prefetch)
}
</script>
<template>
<RouterLink :to custom v-slot="{ isExactActive, href, navigate }">
<a :href="href" @mouseover="prefetch" @focus="prefetch" @click="navigate"
<slot></slot>
</a>
</RouterLink>
</template>
And this is how i want to use it
<script setup lang="ts">
import { authQueries } from '@/queries/auth'

import Link from '@/components/navigation/Link.vue'

const prefetch = authQueries.getMe()
</script>

<template>
<nav>
<Link :to="{ name: 'dashboard' }" :prefetch="prefetch"
>Dashboard</Link
>
</template>
<script setup lang="ts">
import { authQueries } from '@/queries/auth'

import Link from '@/components/navigation/Link.vue'

const prefetch = authQueries.getMe()
</script>

<template>
<nav>
<Link :to="{ name: 'dashboard' }" :prefetch="prefetch"
>Dashboard</Link
>
</template>
This works perfectly, But how can i set the prefetch prop in Link to its correct type? PrefetchQuery expects the type FetchQueryOptions but it has four generics i dont know how to type. Repro: https://codesandbox.io/p/live/2835ffbd-85f5-4048-8035-c307b7516032
8 Replies
sensitive-blue
sensitive-blueOP11mo ago
adding a generic in the Link component like so
<script lang="ts" setup generic="T extends UndefinedInitialQueryOptions">
import { useQueryClient, type UndefinedInitialQueryOptions } from '@tanstack/vue-query'

const props = defineProps<{
to: RouteLocationRaw
prefetch?: T
}>()
<script lang="ts" setup generic="T extends UndefinedInitialQueryOptions">
import { useQueryClient, type UndefinedInitialQueryOptions } from '@tanstack/vue-query'

const props = defineProps<{
to: RouteLocationRaw
prefetch?: T
}>()
will show a type error basically im searching for a type that is accepted by the prefetchQuery function but doesnt care about the return type.
other-emerald
other-emerald11mo ago
Just an idea. If you don't care about return type maybe put "any" into these four generics? Or unknown I can try to confirm this later in TS playground
other-emerald
other-emerald11mo ago
all prefetchQuery really cares about is that it receives an object with queryKey. even queryFn doesn't seem to be required. so props really need to only satisfy { queryKey: QueryKey }. i feel like that's enough type-check and guarantee that you actually pass query options into Link component. it made TS errors go away
sensitive-blue
sensitive-blueOP11mo ago
Thanks! thats actually really clever, I still have the error though but i think im doing something wrong. Let me check with typescript and package versions. https://codesandbox.io/p/live/2835ffbd-85f5-4048-8035-c307b7516032 reproduction, it works in your example but when i use it in a template it doesnt anymore it will not complain if i explicitly do this.
const queryKey: QueryKey = queries.get().queryKey

<HelloWorld :prefetch="{ queryKey }" />
const queryKey: QueryKey = queries.get().queryKey

<HelloWorld :prefetch="{ queryKey }" />
But that will not work since it doesn't include the queryFn.
other-emerald
other-emerald11mo ago
yeah, very strange it still errors in sandbox, maybe vue-tsc issue? i may need to try in vscode locally. but i've found the following this doesn't error: <HelloWorld :prefetch="queries.get()" /> also this doesn't error
const query = toRef(() => queries.get())
// or computed
const query = computed(() => queries.get())
<HelloWorld :prefetch="query" />
const query = toRef(() => queries.get())
// or computed
const query = computed(() => queries.get())
<HelloWorld :prefetch="query" />
sensitive-blue
sensitive-blueOP11mo ago
hm maybe it has something to do with vue-tsc thinking its a ref and tries to automatically unwrap the value in the template. the reason why you don't need to write .value inside the template on refs. writing it directly inside the template like <HelloWorld :prefetch="queries.get()" /> does seem to work indeed. Thanks! I really like Vue with its reactivity system more but stuff like this half baked typescript support of Vue seems to push me more to React.
other-emerald
other-emerald11mo ago
Makes sense. For me personally reactivity, composables and script setup largely outweighs React. I found typescript support is actually quite good, much better than it used to be in Vue 2. There's also tsx if you wanna go fully typescript. Also agreed there are some paper cuts in single file components typescript.

Did you find this page helpful?