@preact-signals/query
acts as a bridge between the core functionality of @tanstack/query-core
and the reactivity provided by @preact/signals
. Designed as a drop-in replacement for @tanstack/react-query
, this library not only mirrors its counterpart's functionalities but also offers enhanced hooks tailored for preact signals.
You should be sure that one of preact signals runtimes installed:
@preact/signals
forpreact
, it requires additional step@preact/signals-react
forreact
Fetch @preact-signals/query
via your preferred package manager:
# Using npm
npm install @preact-signals/query
# Using yarn
yarn add @preact-signals/query
# Using pnpm
pnpm add @preact-signals/query
You should resolve @preact/signals-react
as @preact/signals
To do it take a look at how to resolve react
as preact
and do it with signals. Plus you need to dedupe preact
import preact from "@preact/preset-vite";
import { defineConfig } from "vite";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [preact()],
resolve: {
// add this line
dedupe: ["preact"],
alias: [
{ find: "react", replacement: "preact/compat" },
{ find: "react-dom/test-utils", replacement: "preact/test-utils" },
{ find: "react-dom", replacement: "preact/compat" },
{ find: "react/jsx-runtime", replacement: "preact/jsx-runtime" },
// add this line
{ find: "@preact/signals-react", replacement: "@preact/signals" },
],
},
});
- API Overview
- Query Hooks:
useQuery$, useInfiniteQuery$
- Mutation Hooks:
useMutation$
- Accessing the Client:
useQueryClient$
- Filtering with Hooks:
useIsFetching$
- Configuring suspense behavior for
useQuery$
anduseInfiniteQuery$
- License
Experience the reactive elegance of @tanstack/react-query
with @preact-signals/query
.
Although @preact-signals/query
adopts the API of @tanstack/react-query
, it comes with additional hooks that are specifically optimized for preact signals. You'll recognize these hooks by the appended $
sign:
useQuery$
,useInfiniteQuery$
useMutation$
useQueryClient$
useIsFetching$
Awaited hooks include:
useQueries$
useIsMutating$
useQuery$
stands as the reactive counterpart to useQuery
from @tanstack/react-query
. Instead of the usual reactive object, this hook yields a flat-store.
Primary Differences:
- Adopts the object syntax exclusively.
- Requires a function for
options
that returnsStaticQueryOptions
, as they're executed once initially and then reused when reactivity comes into play. - Results in a flat-store; avoid destructuring.
- Both
suspense
anduseErrorBoundary
are demand-triggered. They're invoked at the exact moment thedata
field is accessed. - As
onError
,onSettled
, andonSuccess
are phased out inreact-query
, these aren't implemented in reactive query hooks.
const isUserRegistered = useSignal(false);
const query = useQuery$(() => ({
queryKey: ["user"],
queryFn: () => fetchUser(),
enabled: isUserRegistered.value,
}));
return (
<>
<button onClick={() => (isUserRegistered.value = !isUserRegistered.value)}>
Toggle Registration
</button>
<Show when={() => query.data}>
{(data) => <div>Name: {data().name}</div>}
</Show>
</>
);
const query = useQuery$(() => ({
queryKey: ["key"],
queryFn: fetchStatistics,
suspense: true,
}));
return (
<>
<Profile />
<Jokes />
{/* Here, only this segment will enter suspense mode */}
<Suspense fallback={<Loader />}>
<Show when={() => query.data}>
{(data) => (
<ul>
{data().map((item) => (
<li key={item.label}>{item.data}</li>
))}
</ul>
)}
</Show>
</Suspense>
</>
);
Functionally similar to the query$ hooks, with a couple of nuances:
- Currently,
useErrorBoundary
isn't available. It's under evaluation for its utility.
const mutation = useMutation$(() => ({
mutationFn: doSomething,
onError: (error) => {
console.error("doSomething failed", error);
},
onSuccess: (data) => {
console.log("wow we've done something", data);
},
}));
return <button onClick={mutation.mutate}>Execute Mutation</button>;
This hook returns the client, encapsulated in signals.
Accepts a reactive callback returning filter options and provides an accessor for the result.
// returns ReadonlySignal<number>
const overallFetching = useIsFetching$(() => null);
const specificFetchCount = useIsFetching$(() => ({
queryFn: ["123"],
}));
return (
<>
<div>Total fetching queries (unoptimized): {overallFetching.value}</div>
<div>
Total fetching queries (optimized, no rerenders): {overallFetching}
</div>
<div>
Fetch count by key (optimized, no rerenders): {specificFetchCount}
</div>
</>
);
If you turn suspense on, the query will fetch after component render (if data
is not accessed) or on the first access of data
. But you can alter this behavior with suspenseBehavior
option (load-on-access
is default).
suspend-eagerly
- executes and suspends the query on mount. Data field will always be loaded. Helpful to be access data without worry about.data
field will throw a Promise.suspend-on-access
- pre executes a query, but suspends only on first access of.data
field. Helpful to suspend child components if passing accessor as prop.load-on-access
- executes query on first access of.data
field or if unused onuseEffect
. Legacy behavior, will be changed tosuspend-on-access
in next major release.
const query = useQuery$(() => ({
queryKey: ["key"],
queryFn: fetchStatistics,
suspense: true,
suspenseBehavior: "suspend-eagerly",
}));
@preact-signals/query
is distributed under the MIT License.