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

# Essential Concepts

> Core concepts and patterns in TanStack Query

# Essential Concepts

Understand the fundamental concepts that make TanStack Query powerful and easy to use.

## Query Keys

Query keys are the foundation of TanStack Query's caching system. They uniquely identify each query in your application.

### Basic Keys

The simplest query key is an array with a single string:

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

### Keys with Variables

Include variables in your query key to create unique cache entries:

```tsx theme={null}
useQuery({
  queryKey: ['todo', todoId],
  queryFn: () => fetchTodo(todoId),
})
```

<Tip>
  Query keys are hashed deterministically, so the order and structure matter. `['todos', 1]` and `['todos', '1']` are different keys.
</Tip>

### Complex Keys

Query keys can include objects for more complex scenarios:

```tsx theme={null}
useQuery({
  queryKey: ['todos', { status: 'done', page: 1 }],
  queryFn: () => fetchTodos({ status: 'done', page: 1 }),
})
```

<Note>
  Object keys are sorted automatically, so `{a: 1, b: 2}` and `{b: 2, a: 1}` produce the same cache key.
</Note>

## Query Functions

The query function is where you fetch your data. It must return a Promise that resolves to data or throws an error.

### Basic Query Function

```tsx theme={null}
const { data } = useQuery({
  queryKey: ['repos'],
  queryFn: async () => {
    const response = await fetch('https://api.github.com/repos/TanStack/query')
    if (!response.ok) {
      throw new Error('Network response was not ok')
    }
    return response.json()
  },
})
```

### Using Query Key in Function

The query function receives a context object with the query key:

```tsx theme={null}
const { data } = useQuery({
  queryKey: ['todo', todoId],
  queryFn: ({ queryKey }) => {
    const [_key, id] = queryKey
    return fetchTodo(id)
  },
})
```

### Signal for Cancellation

Use the AbortSignal for request cancellation:

```tsx theme={null}
const { data } = useQuery({
  queryKey: ['todos'],
  queryFn: ({ signal }) => {
    return fetch('/api/todos', { signal })
  },
})
```

<Warning>
  Always throw errors in your query function instead of returning them. TanStack Query uses thrown errors to determine failure states.
</Warning>

## Stale Time vs Cache Time

Understanding these two concepts is crucial for effective caching:

### Stale Time

`staleTime` determines how long data is considered fresh:

```tsx theme={null}
useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  staleTime: 1000 * 60 * 5, // 5 minutes
})
```

* **Fresh data**: Won't refetch automatically
* **Stale data**: Will refetch in the background when conditions trigger it
* **Default**: `0` (immediately stale)

### Cache Time (gcTime)

`gcTime` (garbage collection time) determines how long unused data stays in cache:

```tsx theme={null}
useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  gcTime: 1000 * 60 * 60, // 1 hour
})
```

* Data remains in cache after all observers unmount
* After `gcTime` expires, data is garbage collected
* **Default**: `5 minutes`

<Note>
  `staleTime` affects when queries refetch. `gcTime` affects when cache entries are removed from memory.
</Note>

## Query Status

Queries can be in one of several states:

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

### Status

* **`pending`** - No cached data and query is currently fetching
* **`error`** - Query encountered an error
* **`success`** - Query succeeded and data is available

### Fetch Status

* **`fetching`** - Query function is executing
* **`paused`** - Query wants to fetch but is paused (offline)
* **`idle`** - Query is not fetching

<Tip>
  Use `isPending` for initial loading states and `isFetching` to show background update indicators.
</Tip>

## Mutations

While queries fetch data, mutations modify data on the server:

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

function TodoForm() {
  const queryClient = useQueryClient()

  const mutation = useMutation({
    mutationFn: (newTodo) => {
      return fetch('/api/todos', {
        method: 'POST',
        body: JSON.stringify(newTodo),
      })
    },
    onSuccess: () => {
      // Invalidate and refetch
      queryClient.invalidateQueries({ queryKey: ['todos'] })
    },
  })

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

### Mutation Status

```tsx theme={null}
const { mutate, isPending, isError, isSuccess, error } = useMutation({
  mutationFn: createTodo,
})

if (isPending) return <Spinner />
if (isError) return <ErrorMessage error={error} />
if (isSuccess) return <SuccessMessage />
```

## Optimistic Updates

Update the UI immediately before the server responds:

```tsx theme={null}
const mutation = useMutation({
  mutationFn: updateTodo,
  onMutate: async (newTodo) => {
    // Cancel outgoing refetches
    await queryClient.cancelQueries({ queryKey: ['todos'] })

    // Snapshot previous value
    const previousTodos = queryClient.getQueryData(['todos'])

    // Optimistically update
    queryClient.setQueryData(['todos'], (old) => [...old, newTodo])

    // Return context with snapshot
    return { previousTodos }
  },
  onError: (err, newTodo, context) => {
    // Rollback on error
    queryClient.setQueryData(['todos'], context.previousTodos)
  },
  onSettled: () => {
    // Refetch after error or success
    queryClient.invalidateQueries({ queryKey: ['todos'] })
  },
})
```

<Warning>
  Always implement error handling with optimistic updates to ensure data consistency if the mutation fails.
</Warning>

## Dependent Queries

Some queries depend on data from other queries:

```tsx theme={null}
function User({ userId }) {
  const { data: user } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
  })

  const { data: projects } = useQuery({
    queryKey: ['projects', user?.id],
    queryFn: () => fetchProjects(user.id),
    enabled: !!user?.id, // Only run when user.id exists
  })
}
```

<Note>
  Use the `enabled` option to control when queries execute based on other data availability.
</Note>

## Query Invalidation

Invalidate queries to mark them as stale and trigger refetches:

```tsx theme={null}
const queryClient = useQueryClient()

// Invalidate specific query
queryClient.invalidateQueries({ queryKey: ['todos'] })

// Invalidate all queries starting with key
queryClient.invalidateQueries({ queryKey: ['todos', 'list'] })

// Invalidate all queries
queryClient.invalidateQueries()
```

## Prefetching

Fetch data before it's needed for better user experience:

```tsx theme={null}
const queryClient = useQueryClient()

function TodosList() {
  const { data: todos } = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
  })

  return (
    <ul>
      {todos.map((todo) => (
        <li
          key={todo.id}
          onMouseEnter={() => {
            // Prefetch todo details on hover
            queryClient.prefetchQuery({
              queryKey: ['todo', todo.id],
              queryFn: () => fetchTodo(todo.id),
            })
          }}
        >
          {todo.title}
        </li>
      ))}
    </ul>
  )
}
```

<Tip>
  Prefetching is great for predictable user navigation patterns, like hovering over links or buttons.
</Tip>

## Next Steps

<CardGroup cols={2}>
  <Card title="TypeScript" icon="code" href="/essentials/typescript">
    Learn how to use TanStack Query with TypeScript
  </Card>

  <Card title="DevTools" icon="wrench" href="/essentials/devtools">
    Set up DevTools to visualize your queries
  </Card>
</CardGroup>
