timmm
timmm•5mo ago

Tanstack + Realtime not working

I'm using tanstack query and I'm not getting updates until I refresh the page Is there anything obvious I'm doing wrong? This pattern was adapted from the example on the tanstack website https://tanstack.com/router/v1/docs/framework/react/examples/start-convex-trellaux Here's the project table
projects: defineTable({
id: v.string(),
ownerUserId: v.id("users"),
name: v.string(),
})
.index("id", ["id"])
.index("ownerUserId", ["ownerUserId"]),
projects: defineTable({
id: v.string(),
ownerUserId: v.id("users"),
name: v.string(),
})
.index("id", ["id"])
.index("ownerUserId", ["ownerUserId"]),
This is my getProject code on the backend
async function ensureProjectExists(
ctx: QueryCtx,
id: string,
): Promise<Doc<"projects">> {
const project = await ctx.db
.query("projects")
.withIndex("id", (q) => q.eq("id", id))
.unique();

invariant(project, `missing project: ${id}`);
return project;
}

export const getProject = query({
args: {
projectId: v.string(),
},
handler: async (ctx, args) => {
const userId = await getAuthUserId(ctx);
if (userId === null) throw new Error("Not authenticated");
const project = withoutSystemFields(
await ensureProjectExists(ctx, args.projectId),
);
if (project.ownerUserId != userId) throw new Error("Not authorized");
return project;
},
});
async function ensureProjectExists(
ctx: QueryCtx,
id: string,
): Promise<Doc<"projects">> {
const project = await ctx.db
.query("projects")
.withIndex("id", (q) => q.eq("id", id))
.unique();

invariant(project, `missing project: ${id}`);
return project;
}

export const getProject = query({
args: {
projectId: v.string(),
},
handler: async (ctx, args) => {
const userId = await getAuthUserId(ctx);
if (userId === null) throw new Error("Not authenticated");
const project = withoutSystemFields(
await ensureProjectExists(ctx, args.projectId),
);
if (project.ownerUserId != userId) throw new Error("Not authorized");
return project;
},
});
This is my queries on the frontend
export const projectQueries = {
list: () => convexQuery(api.projects.getUsersProjects, {}),
getProject: ({ projectId }: { projectId: string }) =>
convexQuery(api.projects.getProject, { projectId }),
};

export function useCreateProjectMutation() {
const mutationFn = useConvexMutation(
api.projects.createProject,
).withOptimisticUpdate((localStore, args) => {
const { project } = args;

const { id: projectId } = project;
const projectQuery = localStore.getQuery(api.projects.getProject, {
projectId,
});
if (projectQuery) {
localStore.setQuery(
api.projects.getProject,
{ projectId },
{
id: project.id,
name: project.initialName,
ownerUserId: projectQuery.ownerUserId,
},
);
}
});

return useMutation({ mutationFn });
}
export const projectQueries = {
list: () => convexQuery(api.projects.getUsersProjects, {}),
getProject: ({ projectId }: { projectId: string }) =>
convexQuery(api.projects.getProject, { projectId }),
};

export function useCreateProjectMutation() {
const mutationFn = useConvexMutation(
api.projects.createProject,
).withOptimisticUpdate((localStore, args) => {
const { project } = args;

const { id: projectId } = project;
const projectQuery = localStore.getQuery(api.projects.getProject, {
projectId,
});
if (projectQuery) {
localStore.setQuery(
api.projects.getProject,
{ projectId },
{
id: project.id,
name: project.initialName,
ownerUserId: projectQuery.ownerUserId,
},
);
}
});

return useMutation({ mutationFn });
}
This is how i render it
export function ProjectChat({ projectId, chatId }: ProjectChatProps) {
const projectQuery = useSuspenseQuery(
convexQuery(api.projects.getProject, {
projectId,
}),
);

if (!projectQuery.data) {
return null;
}

return (
<div className="flex flex-col">
<div className="p-2">{projectQuery.data.name}</div>
<div className="flex">
<div className="w-[500px] flex-shrink-0">{projectQuery.data.name}</div>
<div className="flex-grow">test</div>
</div>
</div>
);
}
export function ProjectChat({ projectId, chatId }: ProjectChatProps) {
const projectQuery = useSuspenseQuery(
convexQuery(api.projects.getProject, {
projectId,
}),
);

if (!projectQuery.data) {
return null;
}

return (
<div className="flex flex-col">
<div className="p-2">{projectQuery.data.name}</div>
<div className="flex">
<div className="w-[500px] flex-shrink-0">{projectQuery.data.name}</div>
<div className="flex-grow">test</div>
</div>
</div>
);
}
5 Replies
timmm
timmmOP•5mo ago
Looking at the network requests in the websocket it's adding the query and then immediately removing it
ballingt
ballingt•5mo ago
Did you see https://docs.convex.dev/client/tanstack-query, there are a couple more steps Oh you based this on one where that's hooked up, got it Are you using the ConvexQueryClient? What you're seeing treats Convex queries like they were fetch requests, just point in time instead of subscriptions
timmm
timmmOP•5mo ago
Yep I'm using the ConvexQueryClient - here's main.tsx
import ReactDOM from "react-dom/client";
import { RouterProvider, createRouter } from "@tanstack/react-router";
import { ConvexQueryClient } from "@convex-dev/react-query";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ConvexAuthProvider } from "@convex-dev/auth/react";
import { ConvexReactClient, useConvexAuth } from "convex/react";
import { api } from "backend/convex/_generated/api";
import "./tailwind.css";

// Setup Convex with Tanstack Query
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(),
},
},
});

// Import the generated route tree
import { routeTree } from "./routeTree.gen";

// Create a new router instance
const router = createRouter({
routeTree,
context: {
api: api,
queryClient: queryClient,
auth: undefined,
},
defaultPreload: "intent",
defaultPreloadStaleTime: 0,
});

// Register the router instance for type safety
declare module "@tanstack/react-router" {
interface Register {
router: typeof router;
}
}

// eslint-disable-next-line react-refresh/only-export-components
function App() {
const auth = useConvexAuth();

return (
<RouterProvider
router={router}
defaultPreload="intent"
context={{
auth,
}}
/>
);
}

// Render the app
const rootElement = document.getElementById("root")!;
if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement);
root.render(
<ConvexAuthProvider client={convex}>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</ConvexAuthProvider>,
);
}
import ReactDOM from "react-dom/client";
import { RouterProvider, createRouter } from "@tanstack/react-router";
import { ConvexQueryClient } from "@convex-dev/react-query";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ConvexAuthProvider } from "@convex-dev/auth/react";
import { ConvexReactClient, useConvexAuth } from "convex/react";
import { api } from "backend/convex/_generated/api";
import "./tailwind.css";

// Setup Convex with Tanstack Query
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(),
},
},
});

// Import the generated route tree
import { routeTree } from "./routeTree.gen";

// Create a new router instance
const router = createRouter({
routeTree,
context: {
api: api,
queryClient: queryClient,
auth: undefined,
},
defaultPreload: "intent",
defaultPreloadStaleTime: 0,
});

// Register the router instance for type safety
declare module "@tanstack/react-router" {
interface Register {
router: typeof router;
}
}

// eslint-disable-next-line react-refresh/only-export-components
function App() {
const auth = useConvexAuth();

return (
<RouterProvider
router={router}
defaultPreload="intent"
context={{
auth,
}}
/>
);
}

// Render the app
const rootElement = document.getElementById("root")!;
if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement);
root.render(
<ConvexAuthProvider client={convex}>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</ConvexAuthProvider>,
);
}
ballingt
ballingt•5mo ago
It looks like you're missing this line
convexQueryClient.connect(queryClient);
convexQueryClient.connect(queryClient);
timmm
timmmOP•5mo ago
That was it. Thanks Tom 🙂

Did you find this page helpful?