> ## 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.

# Query Functions

> Understanding query functions, return values, error handling, and cancellation

Query functions are the core of how TanStack Query fetches data. They are async functions that return data or throw errors.

## Query Function Basics

A query function is any function that returns a Promise:

```tsx theme={null}
import { useQuery } from '@tanstack/react-query'

function usePosts() {
  return useQuery({
    queryKey: ['posts'],
    queryFn: async () => {
      const response = await fetch('https://api.example.com/posts')
      if (!response.ok) {
        throw new Error('Failed to fetch posts')
      }
      return await response.json()
    },
  })
}
```

## Query Function Context

Query functions receive a `QueryFunctionContext` object as their first argument:

```tsx theme={null}
type QueryFunctionContext<TQueryKey extends QueryKey = QueryKey, TPageParam = never> = {
  client: QueryClient      // The QueryClient instance
  queryKey: TQueryKey      // The query key array
  signal: AbortSignal      // AbortSignal for cancellation
  meta: QueryMeta | undefined  // Optional metadata
  pageParam?: unknown      // For infinite queries
}
```

From `types.ts:138-165`, the full context definition:

```typescript theme={null}
export type QueryFunctionContext<
  TQueryKey extends QueryKey = QueryKey,
  TPageParam = never,
> = [TPageParam] extends [never]
  ? {
      client: QueryClient
      queryKey: TQueryKey
      signal: AbortSignal
      meta: QueryMeta | undefined
      pageParam?: unknown
      direction?: unknown
    }
  : {
      client: QueryClient
      queryKey: TQueryKey
      signal: AbortSignal
      pageParam: TPageParam
      direction: FetchDirection
      meta: QueryMeta | undefined
    }
```

### Using the Context

```tsx theme={null}
useQuery({
  queryKey: ['post', postId],
  queryFn: async ({ queryKey, signal }) => {
    const [_key, postId] = queryKey
    
    const response = await fetch(
      `https://api.example.com/posts/${postId}`,
      { signal } // Pass signal for cancellation
    )
    
    return await response.json()
  },
})
```

## Return Values

Query functions must return a Promise that resolves to data:

```tsx theme={null}
// Async/await
queryFn: async () => {
  const response = await fetch('https://api.example.com/posts')
  return await response.json()
}

// Promise chain
queryFn: () => {
  return fetch('https://api.example.com/posts')
    .then(res => res.json())
}

// Direct Promise
queryFn: () => axios.get('/api/posts').then(res => res.data)
```

### Data Validation

From `query.ts:545-556`, TanStack Query validates that data is not undefined:

```typescript theme={null}
const data = await this.#retryer.start()
if (data === undefined) {
  if (process.env.NODE_ENV !== 'production') {
    console.error(
      `Query data cannot be undefined. Please make sure to return a value other than undefined from your query function. Affected query key: ${this.queryHash}`,
    )
  }
  throw new Error(`${this.queryHash} data is undefined`)
}
```

<Warning>
  Query functions **must not** return `undefined`. If your API can return `undefined`, return `null` instead or wrap it in an object.
</Warning>

## Error Handling

### Throwing Errors

Throw errors to indicate failure:

```tsx theme={null}
queryFn: async () => {
  const response = await fetch('https://api.example.com/posts')
  
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`)
  }
  
  return await response.json()
}
```

### Custom Error Objects

Throw custom error objects for better error handling:

```tsx theme={null}
class ApiError extends Error {
  constructor(
    message: string,
    public statusCode: number,
    public response?: any
  ) {
    super(message)
    this.name = 'ApiError'
  }
}

queryFn: async () => {
  const response = await fetch('https://api.example.com/posts')
  
  if (!response.ok) {
    const errorData = await response.json()
    throw new ApiError(
      'Failed to fetch posts',
      response.status,
      errorData
    )
  }
  
  return await response.json()
}
```

### Error Dispatch

From `query.ts:584-600`, errors are dispatched to update query state:

```typescript theme={null}
this.#dispatch({
  type: 'error',
  error: error as TError,
})

// Notify cache callback
this.#cache.config.onError?.(
  error as any,
  this as Query<any, any, any, any>,
)
this.#cache.config.onSettled?.(
  this.state.data,
  error as any,
  this as Query<any, any, any, any>,
)

throw error // rethrow the error for further handling
```

## Request Cancellation

### Using AbortSignal

The `signal` property in the context allows you to cancel requests:

```tsx theme={null}
queryFn: async ({ signal }) => {
  const response = await fetch('https://api.example.com/posts', { signal })
  return await response.json()
}
```

### How Signal Works

From `query.ts:430-443`, the signal is lazily created:

```typescript theme={null}
const abortController = new AbortController()

// Adds an enumerable signal property to the object that
// sets abortSignalConsumed to true when the signal is read.
const addSignalProperty = (object: unknown) => {
  Object.defineProperty(object, 'signal', {
    enumerable: true,
    get: () => {
      this.#abortSignalConsumed = true
      return abortController.signal
    },
  })
}
```

The signal is automatically aborted when:

* The query is cancelled
* The component unmounts (if the signal was consumed)
* A new fetch starts for the same query

<Note>
  TanStack Query only calls `abort()` on the signal if your query function **accesses** the `signal` property. This is an optimization to avoid unnecessary cancellations.
</Note>

### Manual Cancellation

You can manually cancel queries:

```tsx theme={null}
const query = useQuery({
  queryKey: ['posts'],
  queryFn: ({ signal }) => fetchPosts(signal),
})

// Cancel the query
await queryClient.cancelQueries({ queryKey: ['posts'] })
```

From `queryClient.ts:278-291`:

```typescript theme={null}
cancelQueries<TTaggedQueryKey extends QueryKey = QueryKey>(
  filters?: QueryFilters<TTaggedQueryKey>,
  cancelOptions: CancelOptions = {},
): Promise<void> {
  const defaultedCancelOptions = { revert: true, ...cancelOptions }

  const promises = notifyManager.batch(() =>
    this.#queryCache
      .findAll(filters)
      .map((query) => query.cancel(defaultedCancelOptions)),
  )

  return Promise.all(promises).then(noop).catch(noop)
}
```

## Query Function Types

### Type Signature

From `types.ts:96-100`:

```typescript theme={null}
export type QueryFunction<
  T = unknown,
  TQueryKey extends QueryKey = QueryKey,
  TPageParam = never,
> = (context: QueryFunctionContext<TQueryKey, TPageParam>) => T | Promise<T>
```

### Typed Query Functions

```tsx theme={null}
type Post = {
  id: number
  title: string
  body: string
}

const fetchPost: QueryFunction<Post, ['post', number]> = async ({ queryKey }) => {
  const [_key, postId] = queryKey
  const response = await fetch(`https://api.example.com/posts/${postId}`)
  return await response.json()
}

useQuery({
  queryKey: ['post', 123],
  queryFn: fetchPost,
})
```

## Reusable Query Functions

### Factory Pattern

```tsx theme={null}
function createFetcher<T>(url: string) {
  return async ({ signal }: QueryFunctionContext): Promise<T> => {
    const response = await fetch(url, { signal })
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`)
    }
    return await response.json()
  }
}

useQuery({
  queryKey: ['posts'],
  queryFn: createFetcher<Post[]>('https://api.example.com/posts'),
})
```

### Generic Fetcher

```tsx theme={null}
const apiFetcher = async <T>({ 
  queryKey, 
  signal 
}: QueryFunctionContext): Promise<T> => {
  const [_key, ...params] = queryKey
  const url = params.join('/')
  
  const response = await fetch(`https://api.example.com/${url}`, { signal })
  
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`)
  }
  
  return await response.json()
}

useQuery({
  queryKey: ['posts', 'list'],
  queryFn: apiFetcher<Post[]>,
})
```

## Query Function Best Practices

### 1. Always Handle Errors

```tsx theme={null}
// Good
queryFn: async () => {
  const response = await fetch('/api/posts')
  if (!response.ok) throw new Error('Failed to fetch')
  return await response.json()
}

// Bad - doesn't check response.ok
queryFn: async () => {
  const response = await fetch('/api/posts')
  return await response.json() // Could be error HTML
}
```

### 2. Use Signal for Cancellation

```tsx theme={null}
// Good
queryFn: async ({ signal }) => {
  return await fetch('/api/posts', { signal })
}

// Acceptable - if your library doesn't support signals
queryFn: async () => {
  return await axios.get('/api/posts')
}
```

### 3. Extract Query Key Parameters

```tsx theme={null}
// Good
queryFn: ({ queryKey }) => {
  const [_key, postId] = queryKey
  return fetchPost(postId)
}

// Avoid - duplicating parameters
function usePost(postId: number) {
  return useQuery({
    queryKey: ['post', postId],
    queryFn: () => fetchPost(postId), // postId duplicated
  })
}
```

### 4. Type Your Return Values

```tsx theme={null}
// Good - explicit return type
queryFn: async (): Promise<Post[]> => {
  const response = await fetch('/api/posts')
  return await response.json()
}

// Acceptable - inferred return type
queryFn: async () => {
  const response = await fetch('/api/posts')
  return (await response.json()) as Post[]
}
```

## Skip Token

Use `skipToken` to skip query execution:

```tsx theme={null}
import { useQuery, skipToken } from '@tanstack/react-query'

function usePost(postId: number | undefined) {
  return useQuery({
    queryKey: ['post', postId],
    queryFn: postId ? () => fetchPost(postId) : skipToken,
  })
}
```

From `queryClient.ts:616-618`:

```typescript theme={null}
if (defaultedOptions.queryFn === skipToken) {
  defaultedOptions.enabled = false
}
```

<Tip>
  `skipToken` is a cleaner alternative to conditionally setting `enabled: false` when you don't have a valid query function.
</Tip>

## Query Function Context Usage

Real-world example from the basic example (`examples/react/basic/src/index.tsx:84-89`):

```typescript theme={null}
const getPostById = async (id: number): Promise<Post> => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${id}`,
  )
  return await response.json()
}

function usePost(postId: number) {
  return useQuery({
    queryKey: ['post', postId],
    queryFn: () => getPostById(postId),
    enabled: !!postId,
  })
}
```
