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.
TanStack Query for Preact provides hooks for fetching, caching, and updating asynchronous data in your Preact applications. The API is nearly identical to React Query.
Installation
npm install @tanstack/preact-query
# or
pnpm add @tanstack/preact-query
# or
yarn add @tanstack/preact-query
Setup
Wrap your application with QueryClientProvider:
import { QueryClient, QueryClientProvider } from '@tanstack/preact-query'
import { render } from 'preact'
import App from './App'
const queryClient = new QueryClient()
render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>,
document.getElementById('root')!
)
Core Hooks
useQuery
Fetch and cache data with the useQuery hook:
import { useQuery } from '@tanstack/preact-query'
function Todos() {
const { data, isLoading, error } = useQuery({
queryKey: ['todos'],
queryFn: async () => {
const res = await fetch('/api/todos')
return res.json()
},
})
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<ul>
{data.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
)
}
useMutation
Perform side effects with mutations:
import { useMutation, useQueryClient } from '@tanstack/preact-query'
function AddTodo() {
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: async (newTodo) => {
const res = await fetch('/api/todos', {
method: 'POST',
body: JSON.stringify(newTodo),
})
return res.json()
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] })
},
})
return (
<button onClick={() => mutation.mutate({ title: 'New Todo' })}>
{mutation.isPending ? 'Adding...' : 'Add Todo'}
</button>
)
}
useInfiniteQuery
Implement infinite scrolling:
import { useInfiniteQuery } from '@tanstack/preact-query'
function Posts() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
queryKey: ['posts'],
queryFn: async ({ pageParam = 0 }) => {
const res = await fetch(`/api/posts?page=${pageParam}`)
return res.json()
},
getNextPageParam: (lastPage) => lastPage.nextCursor,
initialPageParam: 0,
})
return (
<div>
{data?.pages.map((page) => (
page.posts.map((post) => <Post key={post.id} post={post} />)
))}
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage ? 'Loading...' : 'Load More'}
</button>
</div>
)
}
Suspense Support
useSuspenseQuery
Preact Query has full support for Suspense:
import { Suspense } from 'preact/compat'
import { useSuspenseQuery } from '@tanstack/preact-query'
function TodoList() {
const { data } = useSuspenseQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
})
return (
<ul>
{data.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
)
}
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<TodoList />
</Suspense>
)
}
Use preact/compat to access Suspense features. The API is identical to React’s Suspense.
useSuspenseInfiniteQuery
import { Suspense } from 'preact/compat'
import { useSuspenseInfiniteQuery } from '@tanstack/preact-query'
function InfinitePosts() {
const { data, fetchNextPage, hasNextPage } = useSuspenseInfiniteQuery({
queryKey: ['posts'],
queryFn: ({ pageParam = 0 }) => fetchPosts(pageParam),
getNextPageParam: (lastPage) => lastPage.nextCursor,
initialPageParam: 0,
})
return <PostList data={data} onLoadMore={fetchNextPage} />
}
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<InfinitePosts />
</Suspense>
)
}
Advanced Features
useQueries
Execute multiple queries in parallel:
import { useQueries } from '@tanstack/preact-query'
function UserData({ userIds }) {
const results = useQueries({
queries: userIds.map((id) => ({
queryKey: ['user', id],
queryFn: () => fetchUser(id),
})),
})
const isLoading = results.some((result) => result.isLoading)
return <div>{/* render results */}</div>
}
Query Options Factory
import { queryOptions, useQuery } from '@tanstack/preact-query'
const todoQueries = {
all: () => queryOptions({
queryKey: ['todos'],
queryFn: fetchTodos,
}),
detail: (id: number) => queryOptions({
queryKey: ['todos', id],
queryFn: () => fetchTodo(id),
}),
}
function TodoDetail({ id }) {
const { data } = useQuery(todoQueries.detail(id))
return <div>{data.title}</div>
}
Error Boundaries
import { QueryErrorResetBoundary } from '@tanstack/preact-query'
import { ErrorBoundary } from 'preact-error-boundary'
function App() {
return (
<QueryErrorResetBoundary>
{({ reset }) => (
<ErrorBoundary
onReset={reset}
fallback={(error, resetError) => (
<div>
Error: {error.message}
<button onClick={resetError}>Try again</button>
</div>
)}
>
<YourApp />
</ErrorBoundary>
)}
</QueryErrorResetBoundary>
)
}
Preact-Specific Considerations
Size Optimization
Preact Query is optimized for small bundle sizes, perfect for Preact’s lightweight philosophy:
import { QueryClient, QueryClientProvider } from '@tanstack/preact-query'
// Minimal configuration for smallest bundle
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
refetchOnWindowFocus: false,
},
},
})
Preact Signals Integration
While Preact Query uses hooks, you can integrate with Preact Signals:
import { signal } from '@preact/signals'
import { useQuery } from '@tanstack/preact-query'
const todoId = signal(1)
function TodoDetail() {
const { data } = useQuery({
queryKey: ['todo', todoId.value],
queryFn: () => fetchTodo(todoId.value),
})
return (
<div>
<button onClick={() => todoId.value++}>Next Todo</button>
{data && <h1>{data.title}</h1>}
</div>
)
}
Access signal values with .value when using them in query keys or functions.
Hydration
For SSR with Preact:
import { HydrationBoundary, dehydrate } from '@tanstack/preact-query'
// Server-side
export async function getServerData() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
})
return {
dehydratedState: dehydrate(queryClient),
}
}
// Client-side
function MyApp({ dehydratedState }) {
return (
<QueryClientProvider client={queryClient}>
<HydrationBoundary state={dehydratedState}>
<YourApp />
</HydrationBoundary>
</QueryClientProvider>
)
}
TypeScript
Full TypeScript support:
import { useQuery } from '@tanstack/preact-query'
interface Todo {
id: number
title: string
completed: boolean
}
function Todos() {
const { data } = useQuery({
queryKey: ['todos'],
queryFn: async (): Promise<Todo[]> => {
const res = await fetch('/api/todos')
return res.json()
},
})
// data is typed as Todo[] | undefined
return <div>{data?.length} todos</div>
}
React vs Preact Differences
API Compatibility
The API is virtually identical to React Query:
import { useQuery } from '@tanstack/preact-query'
function Component() {
const query = useQuery({ queryKey: ['data'], queryFn: fetchData })
return <div>{query.data}</div>
}
import { useQuery } from '@tanstack/react-query'
function Component() {
const query = useQuery({ queryKey: ['data'], queryFn: fetchData })
return <div>{query.data}</div>
}
Suspense with Compat
// Import from preact/compat for Suspense features
import { Suspense } from 'preact/compat'
import { useSuspenseQuery } from '@tanstack/preact-query'
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<DataComponent />
</Suspense>
)
}
Automatic Request Cancellation
function SearchResults({ query }) {
const { data } = useQuery({
queryKey: ['search', query],
queryFn: async ({ signal }) => {
const res = await fetch(`/api/search?q=${query}`, { signal })
return res.json()
},
})
return <div>{/* results */}</div>
}
useMutationState
import { useMutationState } from '@tanstack/preact-query'
function GlobalLoadingIndicator() {
const pendingMutations = useMutationState({
filters: { status: 'pending' },
})
if (pendingMutations.length === 0) return null
return <div>Saving {pendingMutations.length} changes...</div>
}
useIsFetching
import { useIsFetching } from '@tanstack/preact-query'
function LoadingBar() {
const isFetching = useIsFetching()
if (!isFetching) return null
return <div class="loading-bar" />
}
Migration from React Query
Migrating from React Query is straightforward:
- Change package imports:
// Before (React)
import { useQuery } from '@tanstack/react-query'
// After (Preact)
import { useQuery } from '@tanstack/preact-query'
- Use
preact/compat for Suspense:
import { Suspense } from 'preact/compat'
- Everything else remains the same.
Preact Query maintains API parity with React Query, making migration seamless.
Use Preact Query DevTools for debugging:
npm install @tanstack/preact-query-devtools
Setup:
import { PrefactQueryDevtools } from '@tanstack/preact-query-devtools'
function App() {
return (
<QueryClientProvider client={queryClient}>
<YourApp />
<PrefactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)
}
Best Practices
Bundle Size
Preact is known for small bundles. Optimize further:
// Import only what you need
import { useQuery } from '@tanstack/preact-query'
// Avoid importing everything
// import * as PreactQuery from '@tanstack/preact-query' ❌
Preact Compat
Use compat mode for maximum React compatibility:
{
"alias": {
"react": "preact/compat",
"react-dom": "preact/compat"
}
}