Convex with TanStack Query
TanStack Query is an excellent, popular library for managing requests to a server.
The @convex-dev/react-query
library provides Query Option functions for use with TanStack Query.
Not all features of the standard Convex React client are available through the TanStack Query APIs but you can use the two alongside each other, dropping into the standard Convex React hooks as necessary.
The TanStack Query adapter is currently a beta feature. If you have feedback or feature requests, let us know on Discord!
This makes subscribing to a Convex query function using the TanStack Query useQuery
hook look like this:
const { data, isPending, error } = useQuery(convexQuery(api.messages.list, {}));
Instead of the typical polling pattern for API endpoints used with TanStack Query, the code above receives updates for this api.messages.list
query from the Convex server reactively. New results for all relevant subscriptions are pushed to the client where they update at the same time so data is never stale and there's no need to manually invalidate queries.
Currently only React Query is supported via @convex-dev/react-query
. Let us know if you would find support for vue-query, svelte-query, solid-query, or angular-query helpful.
Setup
To get live updates in TanStack Query create a ConvexQueryClient
and connect it to the TanStack Query QueryClient. After installing the adapter library with
npm i @convex-dev/react-query
wire up Convex to TanStack Query like this:
import { ConvexQueryClient } from "@convex-dev/react-query";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ConvexProvider, ConvexReactClient } from "convex/react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);
const convexQueryClient = new ConvexQueryClient(convex);
const queryClient = new QueryClient({
defaultOptions: {
queries: {
queryKeyHashFn: convexQueryClient.hashFn(),
queryFn: convexQueryClient.queryFn(),
},
},
});
convexQueryClient.connect(queryClient);
ReactDOM.createRoot(document.getElementById("root")!).render(
<ConvexProvider client={convex}>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</ConvexProvider>,
);
Note that when your create your React tree you should both:
- wrap your app in the TanStack Query
QueryClientProvider
so you can use TanStack Query hooks and - wrap your app in the
ConvexProvider
so you can also use normal Convex React hooks
Queries
A live-updating subscription to a Convex query is as simple as calling TanStack useQuery
with convexQuery
:
import { useQuery } from "@tanstack/react-query";
import { convexQuery } from "@convex-dev/react-query";
import { api } from "../convex/_generated/api";
export function App() {
const { data, isPending, error } = useQuery(
convexQuery(api.functions.myQuery, { id: 123 }),
);
return isPending ? "Loading..." : data;
}
You can spread the object returned by convexQuery
into an object specifying additional arguments of useQuery
.
const { data, isPending, error } = useQuery({
...convexQuery(api.functions.myQuery, { id: 123 }),
initialData: [], // use an empty list if no data is available yet
gcTime: 10000, // stay subscribed for 10 seconds after this component unmounts
});
Mutations
Your app can call Convex mutations by using the TanStack useMutation
hook, and setting the mutationFn
property to the result of calling useConvexMutation
:
import { useMutation } from "@tanstack/react-query";
import { useConvexMutation } from "@convex-dev/react-query";
import { api } from "../convex/_generated/api";
export function App() {
const { mutate, isPending } = useMutation({
mutationFn: useConvexMutation(api.functions.doSomething),
});
return <button onClick={() => mutate({a: "Hello"})}>Click me</button>;
}
useConvexMutation
is just a re-export of the useMutation
hook from Convex React.
Differences from using fetch
with TanStack Query
Convex provides stronger guarantees than other methods of fetching data with React Query, so some options and return value properties are no longer necessary.
Subscriptions to Convex queries will remain active after the last component using useQuery
for a given function unmounts for gcTime
milliseconds. This value is 5 minutes by default; if this results in unwanted function activity use a smaller value.
Data provided by Convex is never stale, so the isStale
property of the return value of useQuery
will always be false. retry
-related options are ignored, since Convex provides its own retry mechanism over its WebSocket protocol. refetch
-related options are similarly ignored since Convex queries are always up to date.