> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/tanstack/query/llms.txt
> Use this file to discover all available pages before exploring further.

# Quick Start

> Get up and running with Vue Query in minutes

# Quick Start

This guide will walk you through the basics of Vue Query, from installation to making your first queries and mutations.

## Prerequisites

Before starting, make sure you have:

* Vue 2.6+ or Vue 3.3+
* Node.js 16+
* Basic familiarity with Vue Composition API

## Installation

<Steps>
  ### 1. Install the Package

  <CodeGroup>
    ```bash npm theme={null}
    npm install @tanstack/vue-query
    ```

    ```bash pnpm theme={null}
    pnpm add @tanstack/vue-query
    ```

    ```bash yarn theme={null}
    yarn add @tanstack/vue-query
    ```
  </CodeGroup>

  ### 2. Set Up the Plugin

  Initialize Vue Query in your main application file:

  ```typescript main.ts theme={null}
  import { createApp } from 'vue'
  import { VueQueryPlugin } from '@tanstack/vue-query'
  import App from './App.vue'

  const app = createApp(App)
  app.use(VueQueryPlugin)
  app.mount('#app')
  ```

  ### 3. Create Your First Query

  Use `useQuery` in any component:

  ```vue TodoList.vue theme={null}
  <script setup>
  import { useQuery } from '@tanstack/vue-query'

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

  const { data, isLoading, error } = useQuery({
    queryKey: ['todos'],
    queryFn: async (): Promise<Todo[]> => {
      const response = await fetch('https://jsonplaceholder.typicode.com/todos')
      if (!response.ok) throw new Error('Failed to fetch todos')
      return response.json()
    },
  })
  </script>

  <template>
    <div>
      <h1>Todo List</h1>
      
      <div v-if="isLoading">Loading todos...</div>
      
      <div v-else-if="error" class="error">
        Error: {{ error.message }}
      </div>
      
      <ul v-else>
        <li v-for="todo in data" :key="todo.id">
          <input type="checkbox" :checked="todo.completed" />
          {{ todo.title }}
        </li>
      </ul>
    </div>
  </template>

  <style scoped>
  .error {
    color: red;
    padding: 1rem;
    background: #ffebee;
    border-radius: 4px;
  }
  </style>
  ```
</Steps>

## Understanding Queries

### Query Keys

Query keys uniquely identify queries in the cache. They can be strings or arrays:

```typescript theme={null}
// Simple string key
const { data } = useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
})

// Array with multiple values
const userId = ref(1)
const { data } = useQuery({
  queryKey: ['todos', userId.value],
  queryFn: () => fetchUserTodos(userId.value),
})

// Complex key with filters
const filters = ref({ status: 'active', page: 1 })
const { data } = useQuery({
  queryKey: ['todos', filters.value],
  queryFn: () => fetchTodos(filters.value),
})
```

<Note>
  When any value in the query key changes, Vue Query automatically refetches the data.
</Note>

### Query Functions

Query functions must return a Promise. They receive a context object with useful information:

```typescript theme={null}
const { data } = useQuery({
  queryKey: ['todo', todoId],
  queryFn: async ({ queryKey, signal }) => {
    const [, id] = queryKey
    
    const response = await fetch(`/api/todos/${id}`, {
      signal, // Automatic cancellation support
    })
    
    if (!response.ok) {
      throw new Error('Network response was not ok')
    }
    
    return response.json()
  },
})
```

### Query Results

`useQuery` returns reactive refs with query state:

```typescript theme={null}
const {
  data,           // Ref<TData | undefined>
  error,          // Ref<TError | null>
  isLoading,      // Ref<boolean> - First load
  isPending,      // Ref<boolean> - Loading state
  isFetching,     // Ref<boolean> - Background refetch
  isSuccess,      // Ref<boolean> - Query succeeded
  isError,        // Ref<boolean> - Query failed
  status,         // Ref<'pending' | 'error' | 'success'>
  fetchStatus,    // Ref<'fetching' | 'paused' | 'idle'>
  refetch,        // Function to manually refetch
} = useQuery({ queryKey: ['todos'], queryFn: fetchTodos })
```

## Working with Reactive Data

Vue Query embraces Vue's reactivity system. Options can be refs, reactive objects, or computed values:

```vue UserProfile.vue theme={null}
<script setup>
import { ref, computed } from 'vue'
import { useQuery } from '@tanstack/vue-query'

const userId = ref(1)
const enabled = ref(true)

// Option 1: Reactive values in options object
const { data: user } = useQuery({
  queryKey: ['user', userId],
  queryFn: () => fetchUser(userId.value),
  enabled, // Query only runs when enabled is true
})

// Option 2: Computed query key
const queryKey = computed(() => ['user', userId.value, 'posts'])
const { data: posts } = useQuery({
  queryKey,
  queryFn: () => fetchUserPosts(userId.value),
})

// Option 3: Options getter function
const { data: profile } = useQuery(() => ({
  queryKey: ['user', userId.value],
  queryFn: () => fetchUser(userId.value),
  enabled: enabled.value,
}))

const nextUser = () => userId.value++
const toggleEnabled = () => enabled.value = !enabled.value
</script>

<template>
  <div>
    <button @click="nextUser">Next User</button>
    <button @click="toggleEnabled">Toggle Enabled</button>
    
    <div v-if="user">
      <h2>{{ user.name }}</h2>
      <p>{{ user.email }}</p>
    </div>
  </div>
</template>
```

<Tip>
  Using reactive options is more efficient than options getter functions. Vue Query tracks dependencies automatically.
</Tip>

## Mutations

Use `useMutation` to create, update, or delete data:

```vue CreateTodo.vue theme={null}
<script setup>
import { ref } from 'vue'
import { useMutation, useQueryClient } from '@tanstack/vue-query'

const queryClient = useQueryClient()
const newTodo = ref('')

const { mutate, isPending, isError, error } = useMutation({
  mutationFn: async (title: string) => {
    const response = await fetch('https://jsonplaceholder.typicode.com/todos', {
      method: 'POST',
      body: JSON.stringify({ title, completed: false }),
      headers: { 'Content-Type': 'application/json' },
    })
    return response.json()
  },
  onSuccess: () => {
    // Invalidate and refetch todos query
    queryClient.invalidateQueries({ queryKey: ['todos'] })
    newTodo.value = ''
  },
})

const handleSubmit = () => {
  if (newTodo.value.trim()) {
    mutate(newTodo.value)
  }
}
</script>

<template>
  <form @submit.prevent="handleSubmit">
    <input 
      v-model="newTodo" 
      placeholder="Enter todo title"
      :disabled="isPending"
    />
    <button type="submit" :disabled="isPending">
      {{ isPending ? 'Adding...' : 'Add Todo' }}
    </button>
    
    <div v-if="isError" class="error">
      Error: {{ error?.message }}
    </div>
  </form>
</template>
```

### Mutation with Optimistic Updates

Update the UI immediately while the mutation is in progress:

```typescript theme={null}
import { useMutation, useQueryClient } from '@tanstack/vue-query'

const queryClient = useQueryClient()

const { mutate } = useMutation({
  mutationFn: updateTodo,
  onMutate: async (updatedTodo) => {
    // Cancel outgoing refetches
    await queryClient.cancelQueries({ queryKey: ['todos'] })
    
    // Snapshot previous value
    const previousTodos = queryClient.getQueryData(['todos'])
    
    // Optimistically update
    queryClient.setQueryData(['todos'], (old) => {
      return old?.map(todo => 
        todo.id === updatedTodo.id ? updatedTodo : todo
      )
    })
    
    // Return context with snapshot
    return { previousTodos }
  },
  onError: (err, variables, context) => {
    // Rollback on error
    if (context?.previousTodos) {
      queryClient.setQueryData(['todos'], context.previousTodos)
    }
  },
  onSettled: () => {
    // Always refetch after error or success
    queryClient.invalidateQueries({ queryKey: ['todos'] })
  },
})
```

## Dependent Queries

Enable queries only when dependencies are ready:

```vue UserPosts.vue theme={null}
<script setup>
import { computed } from 'vue'
import { useQuery } from '@tanstack/vue-query'

const { data: user } = useQuery({
  queryKey: ['user'],
  queryFn: fetchUser,
})

// Only fetch posts when we have a user
const { data: posts } = useQuery({
  queryKey: ['posts', user.value?.id],
  queryFn: () => fetchUserPosts(user.value!.id),
  enabled: computed(() => !!user.value?.id),
})
</script>
```

## Parallel Queries

Execute multiple queries simultaneously:

```typescript theme={null}
import { useQuery } from '@tanstack/vue-query'

const { data: users } = useQuery({
  queryKey: ['users'],
  queryFn: fetchUsers,
})

const { data: posts } = useQuery({
  queryKey: ['posts'],
  queryFn: fetchPosts,
})

const { data: comments } = useQuery({
  queryKey: ['comments'],
  queryFn: fetchComments,
})
```

Or use `useQueries` for dynamic parallel queries:

```typescript theme={null}
import { useQueries } from '@tanstack/vue-query'

const userIds = ref([1, 2, 3])

const queries = useQueries({
  queries: userIds.value.map(id => ({
    queryKey: ['user', id],
    queryFn: () => fetchUser(id),
  })),
})

// queries is an array of query results
const allLoaded = computed(() => 
  queries.value.every(q => q.isSuccess)
)
```

## Infinite Queries

Implement infinite scroll and pagination:

```vue InfiniteList.vue theme={null}
<script setup>
import { useInfiniteQuery } from '@tanstack/vue-query'

interface PageData {
  items: any[]
  nextCursor: number | null
}

const {
  data,
  fetchNextPage,
  hasNextPage,
  isFetchingNextPage,
} = useInfiniteQuery({
  queryKey: ['projects'],
  queryFn: async ({ pageParam = 0 }): Promise<PageData> => {
    const response = await fetch(`/api/projects?cursor=${pageParam}`)
    return response.json()
  },
  initialPageParam: 0,
  getNextPageParam: (lastPage) => lastPage.nextCursor,
})
</script>

<template>
  <div>
    <div v-for="page in data?.pages" :key="page.nextCursor">
      <div v-for="item in page.items" :key="item.id">
        {{ item.name }}
      </div>
    </div>
    
    <button 
      @click="fetchNextPage" 
      :disabled="!hasNextPage || isFetchingNextPage"
    >
      {{ isFetchingNextPage ? 'Loading...' : 'Load More' }}
    </button>
  </div>
</template>
```

## Query Options

Extract and reuse query configurations with `queryOptions`:

```typescript queries/todos.ts theme={null}
import { queryOptions } from '@tanstack/vue-query'

export const todoQueryOptions = (id: number) => queryOptions({
  queryKey: ['todo', id],
  queryFn: () => fetchTodo(id),
  staleTime: 5000,
})

export const todosQueryOptions = queryOptions({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  staleTime: 10000,
})
```

```vue Component.vue theme={null}
<script setup>
import { useQuery } from '@tanstack/vue-query'
import { todoQueryOptions } from '@/queries/todos'

const todoId = ref(1)
const { data } = useQuery(todoQueryOptions(todoId.value))
</script>
```

<Tip>
  `queryOptions` provides perfect TypeScript inference and makes query configurations reusable across queries, prefetching, and server-side code.
</Tip>

## Error Handling

Handle errors at the query level or globally:

```typescript theme={null}
// Per-query error handling
const { data, error, isError } = useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  retry: 3,
  retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
})

// Global error handling
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: 3,
      onError: (error) => {
        console.error('Query error:', error)
        // Show toast notification, etc.
      },
    },
  },
})
```

## Caching Behavior

Control how long data stays fresh and in cache:

```typescript theme={null}
const { data } = useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  staleTime: 1000 * 60 * 5,      // Data is fresh for 5 minutes
  gcTime: 1000 * 60 * 10,         // Cache for 10 minutes after last use
  refetchOnWindowFocus: true,     // Refetch when window regains focus
  refetchOnReconnect: true,       // Refetch when coming back online
})
```

<Note>
  * **staleTime**: How long data is considered fresh (default: 0)
  * **gcTime** (formerly cacheTime): How long unused data stays in cache (default: 5 minutes)
</Note>

## Next Steps

<CardGroup cols={2}>
  <Card title="TypeScript Guide" icon="code" href="/frameworks/vue/typescript">
    Learn about type safety and inference
  </Card>

  <Card title="DevTools" icon="bug" href="/frameworks/vue/devtools">
    Debug queries with Vue DevTools
  </Card>

  <Card title="API Reference" icon="book" href="/api/vue/useQuery">
    Explore the complete API
  </Card>

  <Card title="Examples" icon="flask" href="/examples/vue">
    View real-world examples
  </Card>
</CardGroup>
