Skip to main content

Convex with TanStack Query

If you already use TanStack Query the query options function convexQuery may be useful in addition to the standard Convex React client.

The TanStack Query adapter is in beta

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.

Support for other frameworks

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:

src/main.tsx
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:

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.