Skip to main content
Fetch and cache data with the useQuery composable. It returns a reactive query result that automatically updates when dependencies change.

Signature

function useQuery<TQueryFnData, TError, TData, TQueryKey>(
  options: UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
  queryClient?: QueryClient,
): UseQueryReturnType<TData, TError>

Parameters

options
UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>
required
Configuration options for the query. Can be a reactive ref or getter function.
queryClient
QueryClient
Custom QueryClient instance. If not provided, uses the client from context.

Returns

UseQueryReturnType<TData, TError>
object
Reactive refs containing query state and methods.

Type Parameters

  • TQueryFnData - Type returned by the query function
  • TError - Type of error (defaults to DefaultError)
  • TData - Type of data returned (defaults to TQueryFnData)
  • TQueryKey - Type of the query key (defaults to QueryKey)

Examples

Basic Usage

<script setup>
import { useQuery } from '@tanstack/vue-query'

const { data, isLoading, error } = useQuery({
  queryKey: ['todos'],
  queryFn: async () => {
    const res = await fetch('/api/todos')
    return res.json()
  },
})
</script>

<template>
  <div>
    <div v-if="isLoading">Loading...</div>
    <div v-else-if="error">Error: {{ error.message }}</div>
    <ul v-else>
      <li v-for="todo in data" :key="todo.id">
        {{ todo.title }}
      </li>
    </ul>
  </div>
</template>

Reactive Query Keys

<script setup>
import { ref } from 'vue'
import { useQuery } from '@tanstack/vue-query'

const todoId = ref(1)

// Query automatically refetches when todoId changes
const { data } = useQuery({
  queryKey: ['todo', todoId], // ref is auto-unwrapped
  queryFn: async () => {
    const res = await fetch(`/api/todos/${todoId.value}`)
    return res.json()
  },
})
</script>

<template>
  <div>
    <button @click="todoId++">Next Todo</button>
    <div v-if="data">{{ data.title }}</div>
  </div>
</template>

With TypeScript

<script setup lang="ts">
import { useQuery } from '@tanstack/vue-query'

interface Todo {
  id: number
  title: string
  completed: boolean
}

const { data } = useQuery({
  queryKey: ['todos'],
  queryFn: async (): Promise<Todo[]> => {
    const res = await fetch('/api/todos')
    return res.json()
  },
})

// data is typed as Ref<Todo[] | undefined>
</script>

Conditional Fetching

<script setup>
import { ref } from 'vue'
import { useQuery } from '@tanstack/vue-query'

const userId = ref(null)
const enabled = ref(false)

const { data } = useQuery({
  queryKey: ['user', userId],
  queryFn: () => fetchUser(userId.value),
  enabled, // Query only runs when enabled is true
})
</script>

With Select

<script setup>
import { useQuery } from '@tanstack/vue-query'

const { data } = useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  select: (todos) => todos.filter(todo => !todo.completed),
})

// data only contains incomplete todos
</script>

Shallow Reactivity

<script setup>
import { useQuery } from '@tanstack/vue-query'

// Use shallow: true for large datasets to improve performance
const { data } = useQuery({
  queryKey: ['large-dataset'],
  queryFn: fetchLargeDataset,
  shallow: true, // Prevents deep reactivity
})
</script>

Build docs developers (and LLMs) love