Solid Query

The @tanstack/solid-query package provides a 1st-class API for using TanStack Query with SolidJS.

Example

tsx
import {
QueryClient,
QueryClientProvider,
createQuery,
} from '@tanstack/solid-query'
import { Switch, Match, For } from 'solid-js'
const queryClient = new QueryClient()
function Example() {
const query = createQuery(() => ({
queryKey: ['todos'],
queryFn: fetchTodos,
}))
return (
<div>
<Switch>
<Match when={query.isPending}>
<p>Loading...</p>
</Match>
<Match when={query.isError}>
<p>Error: {query.error.message}</p>
</Match>
<Match when={query.isSuccess}>
<For each={query.data}>{(todo) => <p>{todo.title}</p>}</For>
</Match>
</Switch>
</div>
)
}
function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
)
}

Available Functions

Solid Query offers useful primitives and functions that will make managing server state in SolidJS apps easier.

  • createQuery
  • createQueries
  • createInfiniteQueries
  • createMutation
  • useIsFetching
  • useIsMutating
  • useQueryClient
  • QueryClient
  • QueryClientProvider

Important Differences between Solid Query & React Query

Solid Query offers an API similar to React Query, but there are some key differences to be mindful of.

  • Arguments to solid-query primitives (like createQuery, createMutation, useIsFetching) listed above are functions, so that they can be tracked in a reactive scope.
tsx
// ❌ react version
useQuery({
queryKey: ['todos', todo],
queryFn: fetchTodos,
})
// ✅ solid version
createQuery(() => ({
queryKey: ['todos', todo],
queryFn: fetchTodos,
}))
  • Suspense works for queries out of the box if you access the query data inside a <Suspense> boundary.
tsx
import { For, Suspense } from 'solid-js'
function Example() {
const query = createQuery(() => ({
queryKey: ['todos'],
queryFn: fetchTodos,
}))
return (
<div>
{/* ✅ Will trigger loading fallback, data accessed in a suspense boundary. */}
<Suspense fallback={'Loading...'}>
<For each={query.data}>{(todo) => <div>{todo.title}</div>}</For>
</Suspense>
{/* ❌ Will not trigger loading fallback, data not accessed in a suspense boundary. */}
<For each={query.data}>{(todo) => <div>{todo.title}</div>}</For>
</div>
)
}
  • Solid Query primitives (createX) do not support destructuring. The return value from these functions is a store, and their properties are only tracked in a reactive context.
tsx
import {
QueryClient,
QueryClientProvider,
createQuery,
} from '@tanstack/solid-query'
import { Match, Switch } from 'solid-js'
const queryClient = new QueryClient()
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
)
}
function Example() {
// ❌ react version -- supports destructing outside reactive context
// const { isPending, error, data } = useQuery({
// queryKey: ['repoData'],
// queryFn: () =>
// fetch('https://api.github.com/repos/tannerlinsley/react-query').then(
// (res) => res.json()
// ),
// })
// ✅ solid version -- does not support destructuring outside reactive context
const query = createQuery(() => ({
queryKey: ['repoData'],
queryFn: () =>
fetch('https://api.github.com/repos/tannerlinsley/react-query').then(
(res) => res.json()
),
}))
// ✅ access query properties in JSX reactive context
return (
<Switch>
<Match when={query.isPending}>Loading...</Match>
<Match when={query.isError}>Error: {query.error.message}</Match>
<Match when={query.isSuccess}>
<div>
<h1>{query.data.name}</h1>
<p>{query.data.description}</p>
<strong>👀 {query.data.subscribers_count}</strong>{' '}
<strong>{query.data.stargazers_count}</strong>{' '}
<strong>🍴 {query.data.forks_count}</strong>
</div>
</Match>
</Switch>
)
}
  • Signals and store values can be passed in directly to function arguments. Solid Query will update the query store automatically.
tsx
import {
QueryClient,
QueryClientProvider,
createQuery,
} from '@tanstack/solid-query'
import { createSignal, For } from 'solid-js'
const queryClient = new QueryClient()
function Example() {
const [enabled, setEnabled] = createSignal(false)
const [todo, setTodo] = createSignal(0)
// ✅ passing a signal directly is safe and observers update
// automatically when the value of a signal changes
const todosQuery = createQuery(() => ({
queryKey: ['todos'],
queryFn: fetchTodos,
enabled: enabled(),
}))
const todoDetailsQuery = createQuery(() => ({
queryKey: ['todo', todo()],
queryFn: fetchTodo,
enabled: todo() > 0,
}))
return (
<div>
<Switch>
<Match when={query.isPending}>
<p>Loading...</p>
</Match>
<Match when={query.isError}>
<p>Error: {query.error.message}</p>
</Match>
<Match when={query.isSuccess}>
<For each={query.data}>
{(todo) => (
<button onClick={() => setTodo(todo.id)}>{todo.title}</button>
)}
</For>
</Match>
</Switch>
<button onClick={() => setEnabled(!enabled())}>Toggle enabled</button>
</div>
)
}
function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
)
}
  • Errors can be caught and reset using SolidJS' native ErrorBoundary component. Set throwOnError or the suspense option to true to make sure errors are thrown to the ErrorBoundary

  • Since Property tracking is handled through Solid's fine grained reactivity, options like notifyOnChangeProps are not needed

Want to Skip the Docs?
Query.gg - The Official React Query Course
“This course is the best way to learn how to use React Query in real-world applications.”—Tanner Linsley
Check it out