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

# TypeScript Guide

> Using TanStack Query with TypeScript for full type safety

# TypeScript Guide

TanStack Query is written in TypeScript and provides excellent type safety out of the box.

## Type Inference

TanStack Query infers types automatically from your query functions:

```tsx theme={null}
const { data } = useQuery({
  queryKey: ['repo'],
  queryFn: async () => {
    const response = await fetch('https://api.github.com/repos/TanStack/query')
    return response.json() as Repository
  },
})

// data is automatically typed as Repository | undefined
```

<Tip>
  Use type assertions in your query function to inform TypeScript about the returned data shape.
</Tip>

## Explicit Type Parameters

For more control, specify types explicitly:

```tsx theme={null}
interface Repository {
  name: string
  description: string
  stargazers_count: number
  forks_count: number
}

const { data, error } = useQuery<
  Repository,  // TQueryFnData - Type returned by queryFn
  Error,       // TError - Type of errors
  Repository,  // TData - Type of data (after select)
  ['repo']     // TQueryKey - Type of queryKey
>({
  queryKey: ['repo'],
  queryFn: async () => {
    const response = await fetch('https://api.github.com/repos/TanStack/query')
    return response.json()
  },
})

// data is Repository | undefined
// error is Error | null
```

<Note>
  In most cases, you only need to specify the first type parameter (`TQueryFnData`). The others can be inferred.
</Note>

## Typing Query Keys

Strong typing for query keys helps prevent errors:

```tsx theme={null}
type QueryKey = 
  | ['todos']
  | ['todo', number]
  | ['user', string, { includeProjects: boolean }]

const { data } = useQuery<Todo, Error, Todo, ['todo', number]>({
  queryKey: ['todo', todoId],
  queryFn: ({ queryKey }) => {
    const [_key, id] = queryKey // id is typed as number
    return fetchTodo(id)
  },
})
```

## Using queryOptions Helper

The `queryOptions` helper provides better type inference:

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

interface Post {
  id: number
  title: string
  body: string
}

function postOptions(postId: number) {
  return queryOptions({
    queryKey: ['post', postId],
    queryFn: async (): Promise<Post> => {
      const response = await fetch(`/api/posts/${postId}`)
      return response.json()
    },
    staleTime: 1000 * 60 * 5,
  })
}

// Fully typed, reusable query options
function Post({ postId }: { postId: number }) {
  const { data } = useQuery(postOptions(postId))
  // data is typed as Post | undefined
  
  return <div>{data?.title}</div>
}
```

<Tip>
  Use `queryOptions` to create reusable, well-typed query configurations that can be shared across components.
</Tip>

## Typing Mutations

Mutations support full type safety for variables, data, and errors:

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

interface Todo {
  id: number
  title: string
}

interface CreateTodoVariables {
  title: string
}

function TodoForm() {
  const mutation = useMutation<
    Todo,                    // TData - Mutation response type
    Error,                   // TError - Error type
    CreateTodoVariables,     // TVariables - Variables type
    { previousTodos?: Todo[] } // TContext - Context type
  >({
    mutationFn: async (variables) => {
      // variables is typed as CreateTodoVariables
      const response = await fetch('/api/todos', {
        method: 'POST',
        body: JSON.stringify(variables),
      })
      return response.json()
    },
    onSuccess: (data, variables, context) => {
      // data is typed as Todo
      // variables is typed as CreateTodoVariables
      // context is typed as { previousTodos?: Todo[] }
      console.log('Created:', data.title)
    },
  })

  return (
    <button
      onClick={() => {
        mutation.mutate({ title: 'New Todo' })
      }}
    >
      Add Todo
    </button>
  )
}
```

## Typing the QueryClient

The QueryClient is fully typed and provides type-safe methods:

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

const queryClient = new QueryClient()

function Component() {
  const client = useQueryClient()

  // Type-safe query data access
  const todos = client.getQueryData<Todo[]>(['todos'])
  // todos is Todo[] | undefined

  // Type-safe query data updates
  client.setQueryData<Todo[]>(['todos'], (old) => {
    // old is Todo[] | undefined
    return old ? [...old, newTodo] : [newTodo]
  })
}
```

## Typing Query Results with Initial Data

When providing initial data, the result type changes:

```tsx theme={null}
const { data } = useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  initialData: [] as Todo[],
})

// data is Todo[] (not Todo[] | undefined)
// Because initial data is always present
```

<Note>
  With `initialData`, the `data` property is never `undefined`, making it easier to work with.
</Note>

## Type-Safe Select

Transform query data with full type safety:

```tsx theme={null}
interface Todo {
  id: number
  title: string
  completed: boolean
}

const { data } = useQuery({
  queryKey: ['todos'],
  queryFn: async (): Promise<Todo[]> => {
    const response = await fetch('/api/todos')
    return response.json()
  },
  select: (todos) => {
    // todos is typed as Todo[]
    return todos.filter((todo) => !todo.completed)
  },
})

// data is typed as Todo[] | undefined
```

## Discriminated Unions

Use TypeScript's discriminated unions with query status:

```tsx theme={null}
const result = useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
})

if (result.status === 'pending') {
  // TypeScript knows data is undefined here
  return <Spinner />
}

if (result.status === 'error') {
  // TypeScript knows error is defined here
  return <div>Error: {result.error.message}</div>
}

// TypeScript knows data is defined here
return <div>{result.data.length} todos</div>
```

## Generic Query Hook

Create reusable typed query hooks:

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

function useTypedQuery<TData, TError = Error>(
  key: unknown[],
  fetcher: () => Promise<TData>,
  options?: Omit<UseQueryOptions<TData, TError>, 'queryKey' | 'queryFn'>
): UseQueryResult<TData, TError> {
  return useQuery<TData, TError>({
    queryKey: key,
    queryFn: fetcher,
    ...options,
  })
}

// Usage
interface User {
  id: string
  name: string
}

function useUser(userId: string) {
  return useTypedQuery<User>(
    ['user', userId],
    () => fetchUser(userId),
    { staleTime: 1000 * 60 * 5 }
  )
}
```

## Infinite Queries

Infinite queries have special type requirements:

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

interface Page {
  data: Todo[]
  nextCursor: number | null
}

const { data, fetchNextPage, hasNextPage } = useInfiniteQuery<
  Page,  // TQueryFnData - Single page type
  Error,
  Page,  // TData - After select
  ['todos'],
  number // TPageParam - Page param type
>({
  queryKey: ['todos'],
  queryFn: async ({ pageParam = 0 }) => {
    // pageParam is typed as number
    const response = await fetch(`/api/todos?cursor=${pageParam}`)
    return response.json()
  },
  getNextPageParam: (lastPage) => lastPage.nextCursor,
  initialPageParam: 0,
})

// data.pages is Page[]
```

<Warning>
  Always provide `initialPageParam` when using `useInfiniteQuery` to ensure proper typing.
</Warning>

## Best Practices

<Steps>
  <Step title="Define interfaces for your data">
    Create TypeScript interfaces for all API responses:

    ```tsx theme={null}
    interface User {
      id: string
      name: string
      email: string
    }
    ```
  </Step>

  <Step title="Use queryOptions for reusability">
    Create typed, reusable query configurations:

    ```tsx theme={null}
    const userOptions = (id: string) => queryOptions({
      queryKey: ['user', id],
      queryFn: () => fetchUser(id),
    })
    ```
  </Step>

  <Step title="Let TypeScript infer when possible">
    Avoid over-specifying types. Let TypeScript infer from your query functions.
  </Step>

  <Step title="Use discriminated unions">
    Take advantage of status checks for better type narrowing.
  </Step>
</Steps>

## Common Type Errors

### "Type 'undefined' is not assignable to type..."

This happens when you forget that `data` can be `undefined`:

```tsx theme={null}
// ❌ Wrong
const { data } = useQuery({ queryKey: ['user'], queryFn: fetchUser })
return <div>{data.name}</div> // Error: data might be undefined

// ✅ Correct
const { data } = useQuery({ queryKey: ['user'], queryFn: fetchUser })
if (!data) return null
return <div>{data.name}</div>

// ✅ Also correct with optional chaining
const { data } = useQuery({ queryKey: ['user'], queryFn: fetchUser })
return <div>{data?.name}</div>
```

<Tip>
  Use the `status` field or provide `initialData` to avoid `undefined` checks.
</Tip>

## Next Steps

<CardGroup cols={2}>
  <Card title="DevTools" icon="wrench" href="/essentials/devtools">
    Set up DevTools to inspect your typed queries
  </Card>

  <Card title="Essential Concepts" icon="book" href="/essentials/overview">
    Review core concepts with TypeScript examples
  </Card>
</CardGroup>
