This skill builds on lifecycle/setup-query-client-and-providers and core/design-query-keys-and-options.
Setup
import { queryOptions } from '@tanstack/react-query'
export function todoOptions(id: string) {
return queryOptions({
queryKey: ['todo', id],
queryFn: async () => ({ id }),
})
}
Hooks and Components
Keep Vue refs in the key
import { computed, toRef } from 'vue'
import { useQuery } from '@tanstack/vue-query'
export function useTodo(props: { id: string }) {
const id = toRef(props, 'id')
return useQuery(
computed(() => ({
queryKey: ['todo', id.value],
queryFn: async () => ({ id: id.value }),
})),
)
}
Use Solid option functions
import { createQuery } from '@tanstack/solid-query'
export function useTodo(id: () => string) {
return createQuery(() => ({
queryKey: ['todo', id()],
queryFn: async () => ({ id: id() }),
}))
}
Convert Angular Observable clients to promises
import { injectQuery } from '@tanstack/angular-query-experimental'
import { firstValueFrom, of } from 'rxjs'
export class TodosQuery {
todos = injectQuery(() => ({
queryKey: ['todos'],
queryFn: () => firstValueFrom(of([{ id: 1 }])),
}))
}
Common Mistakes
HIGH Vue ref unwrapped
Wrong:
import { useQuery } from '@tanstack/vue-query'
export function useTodo(id: { value: string }) {
return useQuery({
queryKey: ['todo', id.value],
queryFn: async () => ({ id: id.value }),
})
}
Correct:
import { computed } from 'vue'
import { useQuery } from '@tanstack/vue-query'
export function useTodo(id: { value: string }) {
return useQuery(
computed(() => ({
queryKey: ['todo', id.value],
queryFn: async () => ({ id: id.value }),
})),
)
}
Vue reactive inputs need to stay reactive through the options object or query key.
Source: TanStack/query:docs/framework/vue/reactivity.md
HIGH Svelte store syntax in v6
Wrong:
<script lang="ts">
import { createQuery } from '@tanstack/svelte-query'
const query = createQuery({ queryKey: ['todos'], queryFn: async () => [] })
</script>
Correct:
<script lang="ts">
import { createQuery } from '@tanstack/svelte-query'
const query = createQuery(() => ({
queryKey: ['todos'],
queryFn: async () => [],
}))
</script>
Svelte Query v6 uses rune-compatible option functions rather than the older store shape.
Source: TanStack/query:docs/framework/svelte/migrate-from-v5-to-v6.md
HIGH Angular Observable returned directly
Wrong:
import { injectQuery } from '@tanstack/angular-query-experimental'
import { of } from 'rxjs'
export class TodosQuery {
todos = injectQuery(() => ({
queryKey: ['todos'],
queryFn: () => of([{ id: 1 }]),
}))
}
Correct:
import { injectQuery } from '@tanstack/angular-query-experimental'
import { firstValueFrom, of } from 'rxjs'
export class TodosQuery {
todos = injectQuery(() => ({
queryKey: ['todos'],
queryFn: () => firstValueFrom(of([{ id: 1 }])),
}))
}
Query functions must resolve data; Angular HttpClient Observables need conversion to promises.
Source: TanStack/query:docs/framework/angular/angular-httpclient-and-other-data-fetching-clients.md
See also: core/design-query-keys-and-options for key identity across adapters.