The @tanstack/solid-query
package provides a 1st-class API for using TanStack Query with SolidJS.
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> )}
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
Solid Query offers an API similar to React Query, but there are some key differences to be mindful of.
solid-query
primitives (like createQuery
, createMutation
, useIsFetching
) listed above are functions, so that they can be tracked in a reactive scope.// ❌ react versionuseQuery({ queryKey: ['todos', todo], queryFn: fetchTodos,})
// ✅ solid versioncreateQuery(() => ({ queryKey: ['todos', todo], queryFn: fetchTodos,}))
<Suspense>
boundary.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> )}
createX
) do not support destructuring. The return value from these functions is a store, and their properties are only tracked in a reactive context.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> )}
store
automatically.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
“This course is the best way to learn how to use React Query in real-world applications.”—Tanner LinsleyCheck it out