Data with Convex
Data with Convex in the Next.js to ShadowJS guide.
@shadow-js/convex mirrors convex/react. The function references still come from your generated api
(npx convex codegen). What changes is that results are accessors and flags are properties or accessors. Convex runs
in the browser against its own deployment, so it needs nothing from the server half of Dusk; use it instead of a
loader and a server function when Convex already owns the data.
convex/react |
@shadow-js/convex |
|---|---|
<ConvexProvider client={client}> |
Same, with a client from createConvexClient(url) or new ConvexClient(url) |
useQuery(ref, args) returns data or undefined |
Returns a function: tasks() is the data or undefined. It also has status, isLoading and error properties |
useQuery(ref, "skip") |
Same, and the args may be an accessor: () => (ready() ? args : "skip") |
useMutation(ref) returns a function |
Returns a callable with pending(), error(), reset() and withOptimisticUpdate |
useAction(ref) |
Same shape as useMutation, without optimistic updates |
usePaginatedQuery(ref, args, { initialNumItems }) |
Same name; results(), status(), isLoading(), error() are accessors and loadMore(n) returns a boolean |
useConvexAuth(), Authenticated, Unauthenticated, AuthLoading |
Same names; useConvexAuth() returns isLoading() and isAuthenticated() accessors |
ConvexProviderWithClerk |
ConvexProviderWithAuth with useAuth={clerkConvexAuth({ template: "convex" })} |
preloadQuery and fetchQuery from convex/nextjs |
preloadQuery(http, ref, args) returns { value, token }; use ConvexHttpClient for other one-shot calls |
A Convex mutation replaces a server action, and a Convex query replaces a server component that reads the database.
jsx
"use client";
import { useMutation, useQuery } from "convex/react";
import { api } from "../convex/_generated/api";
export function Tasks() {
const tasks = useQuery(api.tasks.list, {});
const toggle = useMutation(api.tasks.toggle);
if (tasks === undefined) return <p>Loading</p>;
return (
<ul>
{tasks.map((task) => (
<li key={task._id} onClick={() => toggle({ id: task._id })}>{task.title}</li>
))}
</ul>
);
}
Clerk and Convex together#
ConvexProviderWithAuth takes any hook that returns { isLoading, isAuthenticated, fetchAccessToken }, and
clerkConvexAuth builds one from Clerk. Put ClerkProvider above ConvexProviderWithAuth:
src/root.tsxtsx
import { clerkConvexAuth } from "@shadow-js/clerk";
import { ConvexProviderWithAuth, type ConvexClientLike } from "@shadow-js/convex";
import { AuthRoot } from "./auth";
const useConvexAuth = clerkConvexAuth({ template: "convex" });
export function Root(props: { convex: ConvexClientLike; clerk?: import("@shadow-js/clerk").ClerkLike; children?: unknown }) {
return (
<AuthRoot {...(props.clerk === undefined ? {} : { clerk: props.clerk })}>
<ConvexProviderWithAuth client={props.convex} useAuth={useConvexAuth}>
{props.children}
</ConvexProviderWithAuth>
</AuthRoot>
);
}